# Koa 原理解析
参考链接
- 官网: https://koajs.com/
- 源码: https://github.com/koajs/koa
先看两张图:
在洋葱圈模型中, 每一层相当于一个中间件, 用来处理特定的功能. 处理顺序先是 next()
前请求(Request
, 从外层到内层)然后执行 next()
函数, 最后是 next()
后响应(Response
, 从内层到外层), 也就是说每个中间件都有两次处理时机.
# 从源码主流程实现一个简易版 Koa
源码中包含以下主流程:
use()
listen()
重点在于 compose
这个函数
点击查看完整代码
/**
* simple-koa.js
* 简易版 koa
*/
const Emitter = require('events');
const compose = require('./simple-koa-compose');
module.exports = class Koa extends Emitter {
constructor(options) {
super()
options = options || {}
this.middware = []
this.context = {
method: 'GET',
url: '/url',
body: undefined,
set(key, val) {
console.log('context set', key, val);
}
}
}
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function')
// TODO: convert generator function
this.middware.push(fn)
return this
}
listen() {
const fnMiddleware = compose(this.middware)
const ctx = this.context
const handleResponse = () => respond(ctx)
const onerror = () => { console.log('onerror') }
return fnMiddleware(ctx).then(handleResponse).catch(onerror)
}
}
// handle response
function respond(ctx) {
console.log('handle response');
console.log('response end', ctx.body);
}
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
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
# koa-compose 洋葱模型的实现
从代码分析, compose 返回一个 Promise
, Promise
中取出第一个函数(也就是app.use添加的中间件), 传入 context
和第一个 next
函数来执行. 第一个 next
执行后返回的也是 Promise
, Promise再取出第二个函数(中间件), 传入 context
和第二个 next
, 以此类推, 直到最后一个有调用 next
, 则返回 Promise.resolve
, 没有调用则不执行 next
. 这就是洋葱圈模型. 这种把函数存储下来的方式, 在许多开源代码中常见.
点击查看完整代码
/**
* simple-koa-compose.js
* refs: https://github.com/koajs/compose/blob/master/index.js
* @param {function[]} middleware
* @returns {function}
*/
module.exports = 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
* @param {Promise} 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
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)
}
}
}
}
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
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