koa-compose源码分析
koa-compose是koa中间件的核心部分, 控制着中间件的执行流程, 造就了经典的洋葱模型。
module.exports = compose function compose(middleware) { //首先是参数类型检查,不符合就抛错 //middleware必须是数组 if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') //middleware的项必须是函数 for (const fn of middleware) { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } // 返回一个闭包, 保持对 middleware 的引用 return function(context, next) { let index = -1 //从中间件第一项开始,执行中间件函数 return dispatch(0) function dispatch(i) { //索引小于等于index,说明一个中间件函数中next被调用多次,不允许 if (i <= index) return Promise.reject(new Error('next() called multiple times')) //index更新为i,对应前面的检测 index = i //取出中间件中的第i个函数 let fn = middleware[i] //如果索引到数组最后,函数变成next,不明白??? if (i === middleware.length) fn = next //遍历结束,fn为undefined,返回Promise,方便后面thenable if (!fn) return Promise.resolve() try { //关键部分,fn中传入context和next函数,对应于app.use((ctx,next) => {...})的用法 //Promise.resolve让返回值支持thenable调用 return Promise.resolve(fn(context, function next() { //尾递归dispatch,索引加1,不断调用中间件的下一个函数,直至用尽 //尾递归可以提升效率,缩短call Stack //递归是洋葱模型的关键,递归本身的call Stack就是按照洋葱模型来执行 //next之前执行代码,在next时候调用下一个中间件,下一个中间件继续执行next之前代码,next执行下下个中间件 //等到递归完成,到最后一个中间件,开始出栈,先执行最里面的next之后的代码,以此往外执行,形成洋葱调用 return dispatch(i + 1) })) } catch (err) { //如果递归调用中抛错,就reject return Promise.reject(err) } } } }