优化实战 第 35 期 - 优秀源码之中间件设计思想

2,021 阅读1分钟

洋葱模型:(先从皮到心,再从心到皮)把代码流程化,让流水线更加清楚,且每一个中间件都有两次处理时机

onion_model_flow_chart.jpeg

校验参数的合法性

如果参数不是数组,则抛出错误

if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')

如果参数数组中至少有一项不是函数时,则抛出错误

if (middleware.some(fn => typeof fn !== 'function')) {
  throw new TypeError('Middleware must be composed of functions!')
}

如果参数是数组,且数组中的每一项都是函数时,则继续往下执行 dispatch 函数

接收参数并返回 Promise

const dispatch = i => {
  // 通过索引来限制 next() 只能被调用一次;如果 next() 被多次调用,则抛出错误
  if (i <= index) return Promise.reject(new Error('next() called multiple times'))
  index = i
  // 陆续取出 middleware 中的每个函数并开始执行
  let fn = middleware[i]
  // 直到最后一个时,next 为 undefined,此时的 fn 不是函数,直接返回 resolve
  if (i === middleware.length) fn = next
  if (!fn) return Promise.resolve()
  // 如果不是最后一个,则也会返回一个 resolve,但 resolve 的内容是取到下一个 middleware 中的函数
  try {
    // 当执行到 next() 函数时,会调用下一个中间件,会重新 return 进入 dispatch 函数内部,继续执行下一个函数
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
  } catch(err) {
    return Promise.reject(err)
  }
}

核心设计: 嵌套调用 Promise.resolve,往里深入,执行完毕后,从底部再重新往上执行

完整源码(koa-compose)

const compose = (middleware = []) => {
  if (!Array.isArray(middleware)) {
    throw new TypeError('Middleware stack must be an array!')
  }
  return (context, next) => {
    let index = -1
    const dispatch = i => {
      // 通过索引来限制 next() 只能被调用一次;如果 next() 被多次调用,则抛出错误
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      // 陆续取出 middleware 中的每个函数并开始执行
      let fn = middleware[i]
      // 直到最后一个时,next 为 undefined,此时的 fn 不是函数,直接返回 resolve
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      // 如果不是最后一个,则也会返回一个 resolve,但 resolve 的内容是取到下一个 middleware 中的函数
      try {
        // 当执行到 next() 函数时,会调用下一个中间件,会重新 return 进入 dispatch 函数内部,继续执行下一个函数
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
      } catch(err) {
        return Promise.reject(err)
      }
    }
    return dispatch(0)
  }
}

一起学习,加群交流看 沸点