js 中的洋葱模型
来源:SegmentFault 思否
作者:chenwl
这篇文章探讨js的洋葱模型以它的实现原理,洋葱模型顾名思义,指的是方法的执行像洋葱一样,一层一层往里执行,直到中心点后,再一层一层往外出来。

上面的图片取自koa中间件的流程控制图,react-redux的中间件也采用了一样的原理。
let fn1 = (next) => {console.log(1)next()console.log(2)}let fn2 = (next) => {console.log(3)next()console.log(4)}let fn3 = (next) => {console.log(5)next()console.log(6)}const middlewares = [fn1, fn2, fn3];compose(middlewares)();/* result:135642*/
合并后的middlewares数组,经过compose处理打印出来的结果是:1、3、5、6、4、2,我们可以想到compose函数回的函数里面是怎么执行的:
+----------------------------------------------------------------------------------+| || fn1 || || +-----------------------------------------------------------+ || | | || | fn2 | || | | || | +---------------------------------+ | || | | | | || action | action | fn3 | action | action || 1 | 3 | | 4 | 2 || | | action action | | || | | 5 6 | | || | | | | |+----------------------------------------------------------------------------------------------->| | | | | || | | | | || | +---------------------------------+ | || +-----------------------------------------------------------+ |+----------------------------------------------------------------------------------+
那么我们就开始编写compose函数,首先它必须返回的是一个函数,并且每次函数执行,都需要将下一个函数作为参数传给它,这样才能够让方法一层层的执行下去,直到最里面一层:
function compose(middlewarw) {return function(args){dispatch(0);function dispatch(index){let fn = middlewarw[index] || args;if(typeof fn !== "function") return;let next = ()=> dispatch(index+1);fn(next);}}};
当然我们也希望碰上异步函数,也能正常的执行:
function asyncFn() {return new Promise((resolve, reject) => {setTimeout(() => {console.log("delay...");resolve();}, 1000);});}let fn1 = async (next) => {console.log(1)await next()console.log(2)}let fn2 = async (next) => {console.log(3)await asyncFn();await next()console.log(4)}let fn3 = async (next) => {console.log(5)await next()console.log(6)};function compose(middlewarw) {return function (args) {dispatch(0);function dispatch(index) {let fn = middlewarw[index] || args;if (typeof fn !== "function") return Promise.resolve();let next = () => dispatch(index + 1);// 给执行函数添加返回成功的Promise.resolvereturn Promise.resolve(fn(next))}}};compose([fn1,fn2,fn3])();
react-redux 的中间件实现
react-redux的中间件是这样实现compose函数的:
function compose(middlewarw) {return middlewarw.reduce((total, next) => (...args) => total(next(...args)));}
react-redux中间件执行的函数很不好理解,不过我们可以拆开它里面的函数来一步步分析:
let fn1 = (next) => {return ()=>{console.log(1)next()console.log(2)}}let fn2 = (next) => {return ()=>{console.log(3)next()console.log(4)}}let fn3 = (next) => {return ()=>{console.log(5)next()console.log(6)}}let dispatch = compose([fn1,fn2,fn3])(()=> console.log("dispatch"));dispatch();
middlewarw经过reduce叠加,每次都将上一次的结果返回给下一个函数作参数:
// 第1次 reduce 的返回值,变成 total 传递到下一次arg => fn1(() => fn2(arg));// 第2次 reduce 的返回值,继续作为下一次的 totalarg => (arg => fn1(() => fn2(arg)))(() => fn3(arg));
或者将compose转成比较好理解的函数迭代形式:
function compose(middlewarw) {return function(cb) {function dispatch(index){let fn = middlewarw[index];let next = ()=>dispatch(index+1); // 下一次的函数执行// 如果不存在下一个函数了,拿到传参里面的函数执行,这里需要保证传参是一个函数,对应的是redux里面的dispatch参数fn ? fn(next)() : cb()}// 最终返回一个函数return ()=> dispatch(0);}};

评论
