0%

koa-compose 源码分析

koa-compose 的源码分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//@koa-compose v4.1.0
'use strict'

/**
* Expose compositor.
*/

module.exports = compose

/**
* 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) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}

compose 方法的参数为 middleware 数组, 使用 app.use(fn) 时,中间件会被添加到该数组中。

compose 首选会对 middleware 的类型和其保存的每一个中间件的类型进行判断,如果有任一不符合,则抛出错误。

1
2
3
4
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!')
}

然后返回一个匿名函数。匿名函数接受两个参数 context ,nextcontext 为 koa 实例的上下文对象,由 koa\lib\context.js 定义。

匿名函数定义了一个初始值为 -1index 变量,并通过对内部函数 dispatch 的调用,实现 对 index 变量和 dispatch 的闭包。

dispatch 通过 i 变量判断目前是第几个中间件的调用(以 0 开始), 通过 index 变量防止在当前中间件中多次调用 next()

如果 middleware 为空的话直接返回 Promise.resolve(), 否则调用取出的中间件函数并为其传递 contextdispatch.bind(null, i + 1) 函数, 此时就进入了第一个中间件函数的执行。

1
2
3
4
5
6
7
8
9
app.use(function(ctx, next) {
/*
do something...
*/
next();
/*
do something...
*/
})

中间件函数的第一个函数为 context, 第二个参数为 dispatch.bind(null, i + 1) 函数,当执行到 next() 时,就进入到了被 bind i + 1dispatch 中, 重复 dispatch 中的逻辑,取出下一个中间件执行,直到 i=midderware.length, 此时取出的值为 undefined, 导致 if (!fn) return Promise.resolve() 成立,这时就返回到上一个中间函数的以 next() 为分割点的后部分代码往下执行,直至返回到最开始执行的 dispatch(0) 函数被 resolvereject. 这样的执行方式就像洋葱的纹理一样。
onion model