/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
// 中间件列表格式化校验
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!');
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!');
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// 记录上一次执行中间件的位置
let index = -1;
return dispatch(0);
function dispatch (i) {
// 理论上 i 会大于 index,因为每次执行一次都会把 i 递增
// 如果 小于或等于,则说明 next() 执行了多次
// 比如在第一个中间件中 next() 执行两次,此时,两次 dispatch 接收的 i 值都是 2,第一次执行便会 将 2 赋值给 index,第二次执行则会命中 等于 判断
if (i <= index) return Promise.reject(new Error('next() called multiple times'));
index = i;
// 获取当前的中间件
let fn = middleware[i];
// 最后一个中间件中执行 next()
if (i === middleware.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1);
}));
} catch (err) {
return Promise.reject(err);
}
}
}
}
dispatch
函数中,每一个中间件都可以拿到下一个中间件的执行手柄 next
next
,执行多次会出现上面代码中所说的问题async await
的执行机制async function commonMiddleware(ctx, next){
try{
// do something
await next()
// do something
}
.catch(err){
// handle err
}
}
先思考一个问题
如何将多个 平级函数
转化为从右到左的 聚合函数
比如
let fn = compose(fn1,fn2,fn3); // 输出 (...args) => fn1(fn2(fn3(...args)))
// compose聚合函数的顺序是从右到左
const compose = function (...funcs) {
return funcs.reduce((fn1, fn2) => {
return (...args) => {
return fn1(fn2(...args));
};
});
}
看一下 Redux 中间件核心处理链式调用的函数
function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
);
};
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => {
return dispatch(...args);
},
};
// 这里执行了一层中间件接收了{store.getState,dispatch}参数
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// compose(...chain)(store.dispatch) 相当于fn1(fn2(fn3(store.dispatch)))
// 又执行了一层中间件 这一层接收 next 参数 也就是下一个中间件参数
dispatch = compose(...chain)(store.dispatch);
return {
...store, dispatch };
};
}
由以上中间件的处理方式可以看出,一个中间件的基本结构如下
// 中间件逻辑代码需要经过三次柯里化
store => next => action => {
// 中间件逻辑代码
// do something
next(action);
// do something
}
Action
派发后走同步修改操作,唯一有发挥空间的环节在 dispatch
环节dispatch
的增强(拦截处理),使其支持 函数、Promise
的处理以上两种方式都是可以在中间件内部手动调用
next
执行下一个中间件
有时候需要中间件列表是一个自动化的流水线,无需额外控制,自动全链路执行
比如下面的场景,需要中间件模块顺序执行一下
const promiseMiddleware = (middlewares: any[], ctx: any) => {
let promise = Promise.resolve(null);
let next;
// 1. 通过bind把执行上下文对象,绑定到中间件第一个参数
middlewares.forEach((fn, i) => {
middlewares[i] = fn.bind(null, ctx);
});
// 2. 通过while循环执行promise实例
while ((next = middlewares.shift())) {
promise = promise.then(next);
}
// 3. 最终返回一个promise实例结果
return promise.then(() => {
return ctx;
});
}
统一中间件的调用流程
class MiddleWare {
middlewares = [];
ctx = {
message: {
}
}
// 1. 构造器函数,初始化添加 middlewares
constructor(middlewares) {
super();
this.middlewares = middlewares;
}
// 2. 通过批量添加中间件接口
useBatch(steps) {
if (Array.isArray(steps)) {
this.middlewares = this.middlewares.concat(steps);
} else {
throw TypeError('useBatch args must be an arrary!!!')
}
}
// 3. 核心实现,每个Action都需要进过Dispatch进行触发
dispatch(message) {
// 3.1 使用Object.create 创建新的 middlewares 和 ctx对象,防止对象引用
let steps = Object.create(this.middlewares);
let ctx = Object.create(this.ctx);
// 3.2 赋值 消息
ctx.message = message;
// 3.3 执行中间件模块,同时返回一个 promise 实例
return promiseMiddleware(steps, ctx);
}
}
基于 Promise 链式调用的还有 axios 的拦截器处理逻辑
参考文章: