Koa 有两个核心知识点:一个是中间件ctx,一个就是洋葱模型。
中间件ctx利用js原型,很巧妙的把request和response对象封装在里面。详细的实现可看笔者实现的简版koa build-your-own-koa(opens new window)
另外一个核心就是本文要分析的洋葱模型。

1. 认识洋葱模型

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. const PORT = 3000;
  4. // #1
  5. app.use(async (ctx, next)=>{
  6. console.log(1)
  7. await next();
  8. console.log(1)
  9. });
  10. // #2
  11. app.use(async (ctx, next) => {
  12. console.log(2)
  13. await next();
  14. console.log(2)
  15. })
  16. app.use(async (ctx, next) => {
  17. console.log(3)
  18. })
  19. app.listen(PORT);
  20. console.log(`http://localhost:${PORT}`);

打印顺序:

  1. 1
  2. 2
  3. 3
  4. 2
  5. 1

当程序运行到await next()的时候就会暂停当前程序,进入下一个中间件,处理完之后才会回过头来继续处理

2. 原理

核心:中间件管理和next实现,其中next是巧妙的使用了Promise特性。洋葱模型,本质上是Promise.resolve()的递归。

2.1 中间件管理

app.listen使用了this.callback()来生成node的httpServer的回调函数。

  1. listen(...args) {
  2. debug('listen');
  3. const server = http.createServer(this.callback());
  4. return server.listen(...args);
  5. }

Koa.js中间件引擎是有koa-compose模块,即如下的compose方法

  1. callback() {
  2. const fn = compose(this.middleware); // 核心:中间件的管理和next的实现
  3. if (!this.listeners('error').length) this.on('error', this.onerror);
  4. const handleRequest = (req, res) => {
  5. const ctx = this.createContext(req, res); // 创建ctx
  6. return this.handleRequest(ctx, fn);
  7. };
  8. return handleRequest;
  9. }

当我们app.use的时候,只是把方法存在了一个数组里

  1. use(fn) {
  2. this.middleware.push(fn);
  3. return this;
  4. }


2.2 next实现

dispatch函数,它将遍历整个middleware,然后将context和dispatch(i + 1)传给middleware中的方法。
dispatch return Promise这段代码就很巧妙的实现了两点:

  1. 将context一路传下去给中间件
  2. 将middleware中的下一个中间件fn作为未来next的返回值 ```json function compose (middleware) { 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 {

    1. // 核心代码:返回Promise
    2. // next时,交给下一个dispatch(下一个中间件方法)
    3. // 同时,当前同步代码挂起,直到中间件全部完成后继续
    4. return Promise.resolve(fn(context, function next () {
    5. return dispatch(i + 1)
    6. }))

    } catch (err) {

    1. return Promise.reject(err)

    } } } }

  1. <a name="YS4sz"></a>
  2. ## <br />3. 思考
  3. 洋葱模型实现原理,等同于如下代码:<br />**next()返回的是promise,需要使用await去等待promise的resolve值。**<br />promise的嵌套就像是洋葱模型的形状就是一层包裹着一层,直到await到最里面一层的promise的resolve值返回。
  4. ```json
  5. Promise.resolve(middleware1(context, async() => { // 注意async关键字不能省略
  6. return Promise.resolve(middleware2(context, async() => {
  7. return Promise.resolve(middleware3(context, async() => {
  8. return Promise.resolve();
  9. }));
  10. }));
  11. }))
  12. .then(() => {
  13. console.log('end');
  14. });