koa-compose解读

120 阅读2分钟

koa-compose解析

  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!')
  }

检查参数,参数必须是一个数组,且数组中所有元素必须是函数

  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 {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }

compose 函数的返回值仍然是一个函数,且接受两个参数。

let func = compose([Function])
func(context, next)

调用 func 函数,会执行 func 的内部函数 dispatch,并将 dispatch 的返回值作为 func 的返回值

return dispatch(0)

首先执行数组中的第一个函数,数组中的函数也接受两个参数,其中第二个参数仍然为 dispatch 函数,并且其参数值为数组中下一个函数的索引值。假设数组中的函数为 f1...fn,则:

function f1 (ctx, next) {
    console.log(1)
    await next()
    console.log(6)
}
function f2 (ctx, next) {
    console.log(2)
    await next()
    console.log(5)
}
function fn (ctx, next) {
    console.log(3)
    await next()
    console.log(4)
}

数组中的每一个函数都去调用 dispatch ,就可保证整个数组中的函数都会被顺序调用。
如果某个函数没有调用 dispatch 那么这个函数就是数组中最后一个会被调用的函数。
如果数组中最后一个函数也调用了 dispatch 那么,因为数组中已没有下一个函数,此时会把下一个函数设置为 func 的第二个参数,此参数不存在则顺序调用结束,开始反向执行。
通常数组中最后一个函数就不再调用 dispatch

完整代码

'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
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}