koa2第二篇: 图解中间件源码执行过程

中间件

洋葱模型

首先写一个简单的中间件demo:

const Koa = require('koa')
const app = new Koa()
const port = 3000

const ctx1 = async (ctx, next) => {
    console.log('开始执行中间件1')
    await next()
    ctx.response.type = 'text/html'
    ctx.response.body = '<h3>hello world</h3>'
    console.log('结束执行中间件1')
}

app.use(ctx1)
app.use(async function ctx2 (ctx, next) {
    console.log('开始执行中间件2')
    await next()
    console.log('结束执行中间件2')
})

app.listen(port, () => {
    console.log(`server is running on the port: ${port}`)
})

复制代码

很明显中间件执行顺序是这样的:

开始执行中间件1
开始执行中间件2
结束执行中间件2
结束执行中间件1
复制代码

你可以理解为koa2会先按照中间件注册顺序执行next()之前的代码, 执行完到底部之后, 返回往前执行next()之后的代码。

重点是我们需要koa2源码究竟是怎么样执行的? 现在开始调试模式进入koa2源码一探究竟。

  • 首先在两个中间件注册的地方打了断点

  • 我们可以看到koa2是先按照你中间件的顺序去注册执行

扫描二维码关注公众号,回复: 4805092 查看本文章

  • 然后会进入callback. 这是因为
// 应用程序
app.listen(port, () => {
    console.log(`server is running on the port: ${port}`)
})

// 源码
 listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
复制代码

这个时候this.middleware已经存了两个中间件。

  • 这个时候你请求一个路由比如
http://localhost:3000/a
复制代码

koa2的中间件处理就是在这个函数里面

callback() {
    // compose()这是处理中间件的执行顺序所在
  }
复制代码

于是我们进入这个koa-compose的源码看下:

'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
    // 返回一个函数, 从第一个中间件开始执行, 可以通过next()调用后续中间件
    return dispatch(0)
    // dispatch始终返回一个Promise对象
    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 {
        // next即就是通过dispatch(i+1)来执行下一个中间件
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        // 捕获中间件中发生的异常
        return Promise.reject(err)
      }
    }
  }
}

复制代码

此时i=0取出第一个中间件,由于闭包原因i是一直存在的。

这个时候可以看到fn就是ctx1。

注意

// next即就是通过dispatch(i+1)来执行下一个中间件
dispatch.bind(null, i + 1)
复制代码

这个时候开始进入第一个中间件执行第一句console.log('开始执行中间件1')

这里也能看到next指的就是前面提到的dispatch.bind。

然后我们继续单步调试进入这句

// ctx1中的
await next()
复制代码

此时又重新进入compose(), 继续执行下一个中间件, i=1

取出第二个中间件函数ctx2。

此时进入第二个中间件ctx2开始执行console.log('开始执行中间件2')

继续单步调试

此时i=2,fx=undefined

// 这个洋葱模型的最后做一个兜底的处理
if (!fn) return Promise.resolve()
复制代码

执行中间件ctx2的第二句console

猜你喜欢

转载自juejin.im/post/5c31c6edf265da6158774d76