Express Koa Redux 中间件对比

'use strict'

module.exports = compose

function compose(middleware) {
// 每个中间件都是一个 generator or async , 接收 【context 和 next】 两个参数
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
// 取出第 index 个中间件并执行
let fn = middleware[i]
if (i === middleware.length) fn = next
// 如果所有中间件都执行完跳出,并返回一个 Promise
if (!fn) return Promise.resolve()
try {
// 每个中间件都是一个 generator or async , 接收 context 和 next 两个参数
// 每个中间件调用都会在 next 调用处卡住知道递归执行下一个 dispatch ,取出下一个中间件
// 这样只有后面的中间件的 dispatch resolve掉,前面的中间件才会继续执行,最外层的 dispatch(0) 才会 resolve 掉
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}

看源码可以得出:

  1. dispatch方法是递归调用;

  2. 最重要的一行

return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));

一步步解读

  1. fn : 是指中间件,即函数,包含(context 和 next)两个参数;
// 中间件实例
async (ctx, next) => {
console.log('B--')
next()
console.log('--B')
}
  1. context : 不作处理,直接采用外部传进来的;

  2. dispatch.bind(null, i + 1):下一个中间件;

因为dispatch是递归调用,进入下次调用,获取到中间件。

执行步骤

// koa 实例
const Koa = require('koa')
const app = new Koa()

const A_M = async function(ctx, next){
console.log('A--')
await next()
console.log('--A')
}

const B_M = async function(ctx, next){
console.log('B--')
next()
console.log('--B')
}

const C_M = async function(ctx, next){
console.log('C--')
ctx.body = 'hello'
console.log('--C')
}

app.use(A_M);
app.use(B_M);
app.use(C_M);

app.listen(5000, function() {
console.log('请打开: http://127.0.0.1:5000')
})
  1. dispatch(0),Promise.resolve(fn(context, dispatch.bind(null, 0 + 1)))执行,第一个中间件内容将一直运行到 await next()
  2. 执行await next(), next() = dispatch.bind(null, 0 + 1),这是第二个中间件
  3. 第二个中间件将一直运行到 await next()
  4. next()= dispatch.bind(null, 1 + 1),这是第三个中间件
  5. 第三中间件将一直运行到 await next()
  6. next()= dispatch.bind(null, 2 + 1),没有第四种中间件,它将立即返回if (!fn) return Promise.resolve(),
  7. await next()在第三个中间件中解析后,执行第三种中间件中的剩余代码。
  8. await next()在第二中间件resolve了,剩余的则执行第二中间件的代码。
  9. await next()在第一中间件resolve了,剩下的是执行在第一中间件代码。
这也就让我们看到了next函数到底是什么,是dispatch.bind(null, i + 1)。也正是通过dispatch将控制权移交给了下一个中间件。在use中await next()正式将控制权移交给下一个中间件,第一个 => 第二个 => ... => 最后一个,当最后一个中间件执行完毕时,此时,开始执行栈将当前栈顶执行环境出栈,最后一个 => 倒数第二个 => ... =>第一个。也就形成了洋葱模型。

作者:菩提谛听
链接:
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

next方法相当于dispatch.bind(null, i+1),如果中间件不调用next()方法的话,程序执行流将会中断,说的直白一点就是下面一个中间件只有声明的机会却没有执行的机会

1. koa入口文件,`new Koa` -> `use(中间件)`-> `listen()`
2. listen回调函数中会触发**compose**
1. 生成[A_M, B_M, C_M]数组;
3. 调用递归函数, 以下步骤主要涉及:**洋葱模型**
1. 调用dispatch(0), 获取到:Promise.resolve(fn(context, dispatch.bind(null, 0 + 1))); 即获取中间件 A_M;
2. 输出中间件中的:A--
3. 触发中间件中的: await next(), 因为`next() = dispatch.bind(null, 0 + i)`
4. 回调3.1 dispatch(1), 得到中间件 B_M;
5. 执行3.2 3.3;
6.

参考链接
JavaScript 的 this 原理
傻瓜式解读koa中间件处理模块koa-compose
koa-compose源码解读
Redux,Koa,Express之middleware机制对比
Express, Koa, Redux中间件的区别,写法和执行流程