Koa 原理解析

# Koa 原理解析

参考链接

  • 官网: https://koajs.com/
  • 源码: https://github.com/koajs/koa

先看两张图:

koa模型

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

# 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