收集
new 一个koa实例后,调用use接口会将传入的中间件函数push到middleware属性数组中做收集1
2
3
4
5
6
7
8
9
10
11
12
13
14application.js
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
if (isGeneratorFunction(fn)) {
deprecate('Support for generators will be removed in v3. ' +
'See the documentation for examples of how to convert old middleware ' +
'https://github.com/koajs/koa/blob/master/docs/migration.md');
fn = convert(fn);
}
debug('use %s', fn._name || fn.name || '-');
this.middleware.push(fn);
return this;
}
根据koa框架版本不同,需要将使用generator函数写的中间件转化成基于promise的函数形式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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85const convert = require('koa-convert');
function convert (mw) {
if (typeof mw !== 'function') {
throw new TypeError('middleware must be a function')
}
//再次确认是否是generator函数
if (mw.constructor.name !== 'GeneratorFunction') {
// 假设它是一个基于promise的中间件
// 就直接返回
return mw
}
//如果是generator函数就转换成promise形式
const converted = function (ctx, next) {
return co.call(ctx, mw.call(ctx, createGenerator(next)))
}
converted._name = mw._name || mw.name
return converted
}
const co = require('co')
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
整合
调用listen函数开启服务时,整合所有中间件为一个大函数,大函数中进行剥洋葱流程
在收到请求时调用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
32application.js
listen(...args) {
debug('listen');
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback() {
const fn = compose(this.middleware); //整合中间件
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn); //调用中间件
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
//通过传递过来的ctx,获取到原生的可写流
const res = ctx.res;
//设置默认状态码
res.statusCode = 404;
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
//调用中间件大函数 同时准备catch处理中间件级错误
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
整合中间件 ,返回大函数,大函数中有剥洋葱模型1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function 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!')
}
//调用时fnMiddleware(ctx).then(handleResponse).catch(onerror);
//next 是undefined
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 {//try-catch 用于保证错误在promise的情况能够正常捕获
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
剥洋葱流程
1 | let index = -1 |
利用await暂停当前中间件执行,调用next开始执行下一个中间件
如果下一个中间件没有调next,且不是最后一个中间件,
即不会再调用dispatch执行下下一个中间件,则后续中间件都不会被执行到
如果下一个中间件是最后一个中间件,且在其中调用了next,则不会走到这个流程,
在上面的流程中就return
如果下一个中间件是最后一个中间件,则执行完return Promise.resolve
上一个中间件await 拿到结果后继续执行await后面的代码
执行完await后面代码后,上上一个中间件await 拿到结果,继续执行当前中间件await后代码
依次类推,直到第一个中间件await后代码执行完毕,整个中间件流程,
即剥洋葱流程先内层后外层执行流程完毕
返回promise 接着then处理响应或者中间有错误捕获错误
fnMiddleware(ctx).then(handleResponse).catch(onerror)