koa-compose 源码学习

97 阅读2分钟

前言

koa-compose

源码地址: koa-compose

代码地址: koa-compose/index.js

可以使用项目里的 test/test.js测试

源码解析

'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
    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
      // 如果迭代完了所有的中间件则返回一个成功的Promise
      if (!fn) return Promise.resolve()
      try {
        // 执行fn(即当前中间件)并且设置下标指向下一个中间件
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

代码不多,核心是

  1. 接收一个中间件数组
  2. 返回一个迭代器,该迭代器负责迭代中间件数组
  3. 根据下标来执行异步中间件,每执行一个中间件,就把数组下标后移,并从数组中取出下一个作为下一次迭代的项,然后等待当前项异步执行

洋葱模型

const Koa = require('koa')

const app = new Koa()
// Response 时间
app.use(async (ctx, next) => {
  const start = Date.now()
  console.log(1)
  await next()
  const ms = Date.now() - start
  ctx.set('X-Response-Time', ms)
  console.log(5)
})
// logger
app.use(async (ctx, next) => {
  const start = Date.now()
  console.log(2)
  await next()
  const ms = Date.now() - start
  // 写入logger
  // log(`${ctx.method} ${ctx.url} - ${ms}`);
  console.log(4)
})
// 设置body
app.use(async (ctx) => {
  ctx.body = 'Koa 洋葱模型'
   console.log(3)
})

这代码会依次打印: 1 2 3 4 5

可以看到洋葱模型是结合了 async await 实现的

总结

这个中间件迭代器代码不多,核心原理归结如下

  1. 实现一个函数 composecompose 接收一个中间件数组 middleware,返回一个中间件迭代器
  2. 迭代器接收两个参数:上下文context, 下一个中间件next
  3. 迭代器内部有个dispatch函数,该函数接受middleware下标,负责执行下标指定的中间件,并且将下标指向下一个中间件(next的由来),如果没有下一个了则返回 Promise.resolve()
  4. next的调用时机是在上一个中间件里处理完它的事务后负责调用的,由此实现了整个异步迭代

关于洋葱模型,这里是 async await带来的特性,因为每个中间件 await next() 前的代码直接执行,后面的代码都需要等待下一个中间件next所有代码执行完才执行,这才形成了完整的洋葱调用模型