洋葱模型:(先从皮到心,再从心到皮)把代码流程化,让流水线更加清楚,且每一个中间件都有两次处理时机
校验参数的合法性
如果参数不是数组,则抛出错误
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)
}
}
一起学习,加群交流看 沸点