7.2.4 异步操作的中止
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
7.2.4 异步操作的中止
对于访问服务器这样的异步操作,从发起操作到操作结束,都会有段时间延迟,在这段延迟时间中,用户可能希望中止异步操作。
从执行一个fetch语句发出请求,到获得服务器返回的结果,可能很快只有几十毫秒,也可能要花上好几秒甚至十几秒,如果没有超时限制的话,就算是等上几分钟也完全是可能的。也就是说,从一个请求发出到获得响应这个过程中,用户可能等不及了,或者改变主意想要执行另一个操作,用户就会进行一些操作引发新的请求发往服务器,而这就是我们开发者需要考虑的问题。
在weather_redux这个应用中,如果当前城市是“北京”,用户选择了“上海”之后,不等服务器返回,立刻又选择“广州”,那么,最后显示出来的天气是什么呢?
结果是难以预料的,用户的两次选择城市操作,引发了两次API请求,最后结果就看哪个请求先返回结果。假如是关于“上海”的请求先返回结果,界面上就会先显示上海的天气信息,然后关于“广州”的请求返回,界面上又自动更新为广州的天气信息。假如是关于“广州”的请求先返回,关于“上海”的请求后返回,那么结果就正相反,最后显示的是上海的天气信息。此时界面上会出现严重的信息不一致,城市选择器上显示的是“广州”,但是天气信息部分却是“上海”。
两次API的请求顺序是“上海”“广州”,有可能返回的顺序是“广州”“上海”吗?完全可能,访问服务器这样的输入输出操作,复杂就复杂在返回的结果和时间都是不可靠的,即使是访问同样一个服务器,也完全可能先发出的请求后收到结果。
要解决这种界面上显示不一致的问题,一种方法是在视图上做文章,比如当一个API请求发出去,立刻将城市选择器锁住,设为不可改变,直到API请求返回结果才解锁。这种方式虽然可行,但是给用户的体验可能并不好,用户希望随时能够选择城市,而服务器的响应时间完全不可控,锁住城市选择器的时间可能很长,而且这个时间由服务器响应时间决定,不在代码控制范围内,如果服务器要等10秒钟才返回结果,锁住城市选择器的时间就有10秒,这是不可接受的。
从用户角度出发,当连续选择城市的时候,总是希望显示最后一次选中的城市的信息,也就是说,一个更好的办法是在发出API请求的时候,将之前的API请求全部中止作废,这样就保证了获得的有效结果绝对是用户的最后一次选择结果。
在jQuery中,可以通过abort方法取消掉一个AJAX请求:
const xhr = $.ajax(...);
xhr.abort(); //取消掉已经发出的AJAX请求
但是,很不幸,对于fetch没有对应abort函数的功能,因为fetch返回的是一个Promise对象,在ES6的标准中,Promise对象是不存在“中断”这样的概念的。
既然fetch不能帮助我们中止一个API请求,那就只能在应用层实现“中断”的效果,有一个技巧可以解决这个问题,只需要修改action构造函数。
我们对src/weather/actions.js进行一些修改,代码如下:
let nextSeqId = 0;
export const fetchWeather = (cityCode) => {
return (dispatch) => {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
const seqId = ++ nextSeqId;
const dispatchIfValid = (action) => {
if (seqId === nextSeqId) {
return dispatch(action);
}
}
dispatchIfValid(fetchWeatherStarted())
fetch(apiUrl).then((response) => {
if (response.status !== 200) {
throw new Error('Fail to get response with status ' + response.status);
}
response.json().then((responseJson) => {
dispatchIfValid(fetchWeatherSuccess(responseJson.weatherinfo));
}).catch((error) => {
dispatchIfValid(fetchWeatherFailure(error));
});
}).catch((error) => {
dispatchIfValid(fetchWeatherFailure(error));
})
};
}
在action构造函数文件中定义一个文件模块级的nextSeqId变量,这是一个递增的整数数字,给每一个访问API的请求做序列编号。
在fetchWeather返回的函数中,fetch开始一个异步请求之前,先给nextSeqId自增加一,然后自增的结果赋值给一个局部变量seqId,这个seqId的值就是这一次异步请求的编号,如果随后还有fetchWeather构造器被调用,那么nextSeqId也会自增,新的异步请求会分配为新的seqId。
然后,action构造函数中所有的dispatch函数都被替换为一个新定义的函数dispatchIf-Valid,这个dispatchIfValid函数要检查一下当前环境的seqId是否等同于全局的nextSeqId。如果相同,说明fetchWeather没有被再次调用,就继续使用dispatch函数。如果不相同,说明这期间有新的fetchWeather被调用,也就是有新的访问服务器的请求被发出去了,这时候当前seqId代表的请求就已经过时了,直接丢弃掉,不需要dispatch任何action。
虽然不能真正“中止”一个API请求,但是我们可以用这种方法让一个API请求的结果被忽略,达到了中止一个API请求一样的效果。
在这个例子中Weather模块只有一种API请求,所以一个API调用编号序列就足够,如果需要多种API请求,则需要更多类似nextSeqId的变量来存储调用编号。
拥有异步操作中止功能的代码,在本书Github代码库https://github.com/mocheng/react-and-redux/的chapter-07/weather_redux_improved目录下找到,启动对应的应用,可以看到无论如何选择城市,最终显示的天气信息和选中的城市都是一致的。 深入浅出React和Redux