9.1.3 Promise中间件
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
9.1.3 Promise中间件
理解中间件的最好办法就是自己开发一个中间件,在上一章中提到过,替换redux-thunk中间件来实现异步action对象还有一个办法是利用Promise,Promise更加适合于输入输出操作,而且fetch函数返回的结果就是一个Promise对象,接下来我们就实现一个用于处理Promise类型action对象的中间件。
一个最简单的Promise中间件实现方式是这样:
function isPromise(obj) {
return obj && typeof obj.then === 'function';
}
export default function promiseMiddleware({dispatch}) {
return function(next) {
return function(action) {
return isPromise(action) ? action.then(dispatch) : next(action);
}
}
}
逻辑很简单,类似于redux-thunk判断action对象是否是函数,在这里判断action对象是不是一个Promise。如果不是,那就放行通过next让其他中间件继续处理。如果是,那就让这个Promise类型的action对象在完成时调用dispatch函数把完成的结果派发出去。
这个实现很简单,但并不十分实用。
我们在第7章的Weather应用中说到过,做一个完备的异步操作,要考虑三个状态,分别是“异步操作进行中”、“异步操作成功完成”和“异步操作失败”。上面的Promise中间件只考虑了第二种状态“异步操作成功完成”,这是不完整的,使用这样的Promise中间件,并不比redux-thunk省事。
我们需要重新思考一下如何使用Promise。
对于redux-thunk,实现异步就是要在中间件层执行一个指定子程序,而redux-thunk就是用action对象是不是函数类型来判断是否调用子程序。严格来说,我们完全可以写一个中间件,通过判断action对象上的async或者什么其他字段,代码如下:
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action.async === 'function') {
return action.async(dispatch, getState);
}
return next(action);
};
然后只要约定异步action对象依然是普通JavaScript对象,只是多一个名为async的函数类型字段。
如果能够这样理解action对象,那么我们也没有必要要求Promise中间件处理的异步action对象是Promise对象了,只需要action对象某个字段是Promise字段就行,而action对象可以拥有其他字段来包含更多信息。
改进版本的Promise中间件是这样:
export default function promiseMiddleware({dispatch}) {
return (next) => (action) => {
const {types, promise, ...rest} = action;
if (!isPromise(promise) || !(action.types && action.types.length === 3)) {
return next(action);
}
const [PENDING, DONE, FAIL] = types;
dispatch({...rest, type: PENDING});
return action.promise.then(
(result) => dispatch({...rest, result, type: DONE}),
(error) => dispatch({...rest, error, type: FAIL})
);
};
}
这个中间件处理的异步对象必须包含一个promise字段和一个types字段,前者当然是一个Promise对象,后者则必须是一个大小为3的数组,依次分别代表异步操作的进行中、成功结束和失败三种action类型。具体是什么值,应该在对应功能组件的action-Types.js文件中定义:
一个被Promise中间件处理的异步action对象的例子是这样:
{
promise: fetch(apiUrl),
types: ['pending', 'success', 'failure']
}
如果传入的action对象不满足这种格式,就直接通过next交给其他中间件处理。
如果确定传入的action对象满足条件,那么Promise中间件就从action对象的types字段提取出三个action类型,第一个是表示异步操作进行中的PENDING,不用多说,先制造一个type为PENDING的action对象派发出去,告诉系统这个异步动作已经开始了。
接下来,通过then和catch分别连接上promise字段,当这个字段代表的Proimse对象成功完成时,派发一个type为DONE的action对象,失败的时候派发一个type为FAIL的action对象。
在这里PENDING、DONE和FAIL都是变量,是最初异步对象中types字段提取出来的,由异步动作的发起者决定,所以这一个Promise中间件能够支持任意种类的异步请求。
这里也有一个约定,所有表示异步操作成功的action对象由result字段记录成功结果,表示异步操作失败的action对象用error字段记录失败原因。
有了上面的Promise中间件,可以大大简化使用异步动作的模块代码。
在第7章中的Weather应用,如果我们用这个Promise中间件取代redux-thunk,那么weather组件的actions.js文件可以改写,对应fetchWeather的动作构造函数代码如下:
export const fetchWeather = (cityCode) => {
const apiUrl = `/data/cityinfo/${cityCode}.html`;
return {
promise: fetch(apiUrl).then(response => {
if (response.status !== 200) {
throw new Error('Fail to get response with status ' + response.status);
}
return response.json().then(responseJson => responseJson.weatherinfo);
}),
types: [FETCH_STARTED, FETCH_SUCCESS, FETCH_FAILURE]
};
}
原来的三个action构造函数fetchWeatherStarted、fetchWeatherSuccess和fetchWeather-Failure都不见了,因为他们的逻辑都被抽象出来放到Promise中间件中去了,在这个文件中只需要通过异步action对象的types字段把三个定义好的action类型设上就可以。
对比redux-thunk中间件和这个Promise中间件可以发现区别,如果应用redux-thunk,实际发起异步操作的语句是在中间件中调用的;而如果应用Promise中间件,异步操作是在中间件之外引发的,因为只有异步操作发生了才会有Promise对象,而Promise中间件只是处理这个对象而已。
读者可能会发现,Promise中间件要求“异步操作成功”和“异步操作失败”两个action对象必须用result和error字段记录结果,这样缺乏灵活性,的确是的。有一个改进方法是不用types传入action类型,而是用例如actionCreators名字的字段传入三个action构造函数,这样就能够更灵活地产生action对象,代价就是每个模块需要定义那三个action构造函数,读者可以尝试实现这种中间件。 深入浅出React和Redux