Express与Koa中间件机制分析(二)

前言

Express与Koa中间件机制分析(一)中咱们有提到,Express 为线型模型,而 Koa 则为洋葱型模型,以前咱们已经经过解析 connect 的源码对 Express 中间件机制进行了分析,本篇文章咱们将对 Koa 的部分源码进行分析以帮助你们来理解其中间件机制。javascript

koa1 基于的 co 库,因此 koa1 利用 Generator 来代替回调,而 koa2 因为 node 对 async/await 的支持,因此 koa2 利用的是 async/awaitjava

目前你们经常使用的基本上都是 koa2,在这里咱们先对于 Koa2 的实现进行分析,Koa1 先留个坑,以后再补充。node

Koa1 中间件

留坑待续...api

Koa2 中间件

Koa2 中间件的实现依赖于其自身的 koa-compose,下面咱们看一下 compose 函数的实现。数组

function compose (middleware) {
  // 判断参数是否合法,middleware 要求为数组且其中每一个数组元素都为 function
  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
    // 递归返回一个函数 该函数返回一个 Promise 的对象
    return dispatch(0)
    function dispatch (i) {
      // 当 next 方法被屡次调用时会出现
      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()
      // Promise 封装中间件 进行递归调用
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
复制代码

在 Koa 中的 use 函数内,中间件入栈,在 compose 函数中传入的 middleware 即为中间件数组。在 compose 中对该数组进行递归调用,返回一个 Promise 链。koa

下面经过一个例子来对中间件执行这一过程进行分析:async

// 中间件 fn1 和 fn2
async function fn1 (ctx, next) {
  console.log('first: start')
  await next()
  console.log('first: end')
}
async function fn2 (ctx, next) {
  console.log('second: start')
  await next()
  console.log('second: end')
}
// 模拟中间件数组
const arr = [fn1, fn2]
// 执行函数,这里返回的是一个 Promise 对象
compose(arr)()
// 执行后的结果
// first: start
// second: start
// second: end
// first: end
复制代码

其实,在 compose 内部递归执行的操做后,造成多个 Promise 层层嵌套(以下面代码所示),此时 next 函数其实就是下一个中间件,await 须要等待内部的 Promise ,因此其执行结果会呈现一个剥洋葱的模式。函数

function mycompose() {
  return function () {
    const ctx = {}
    return Promise.resolve(fn1(ctx, () => {
      return Promise.resolve(fn2(ctx, () => {
      }))
    }))
  }
}
mycompose()()
复制代码

总结

Koa 相对于 Express 来讲轻量了好多,它只是一个基础的结构,当须要到其余功能时,再使用相应的中间件,上文所讲到的 koa-compose 就是其中之一,其路由及其余功能又会在其余中间件实现,因此你能够看到,即便是 koa-compose 所有的代码依然很简练。post

相关文章
相关标签/搜索