总有一些js代码写出来让人啧啧称奇,就比如koa-compose的源码:
koa-compose就是用来处理koa的中间件的,其主要代码就是如下函数:
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) {
/* 这两句话的意思是来防止我们执行两次`next()`,试想一下:当我们第一次执行`next()`的时候,`index=i`然后第二次在执行,由于两次的dispatch.bind(null, i + 1)是相同的,所以执行第二次`next()`的时候`index==i`于是if条件就会成立,然后报错。 */
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i] //让fn指向
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)
}
}
}
}
仔细研究这个函数就可以发现:
- 首先对于函数行参进行类型判断,类型必须是一个函数数组,否则报错
- 返回一个闭包(仔细观察可以发现返回的匿名函数使用了变量middleware),这使得compose函数的作用域一直存在(暂时不知道有什么意义???
为什么不把middleware作为一个参数写到这个匿名function的行参呢?大概是为了防止外部修改middleware?
可是这样一想的话为什么不用bind来绑定这个参数为middleware呢?突然又发现简单bind的话,会影响原先的context和next参数)
正式看看这个函数并,结合以下代码来测试:
function a(ctx,next){
console.log("111")
next()
// next()
}
function b(ctx,next){
console.log("222")
next()
}
function c(ctx,next){
console.log("333")
next()
}
compose([a,b,c])().then(()=>{
console.log("over")
})
当我们compose([a,b,c])
的时候返回了一个函数,然后再()
调用这个函数,在这个函数中就会执行dispatch(0)
,
我么来仔细看看dispatch函数:
function dispatch (i) {
/* 这两句话的意思是来防止我们执行两次`next()`,试想一下:当我们第一次执行`next()`的时候,`index=i`然后第二次在执行,由于两次的dispatch.bind(null, i + 1)是相同的,所以执行第二次`next()`的时候`index==i`于是if条件就会成立,然后报错。 */
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i] //让fn指向当前该执行的函数,也就是我们写的中间件
if (i === middleware.length) fn = next //当middleware中的函数执行完毕后(也就是所有的中间件都执行完了)去执行next这个行参传进来的函数
if (!fn) return Promise.resolve()// 如果next参数为空,且当middleware执行完毕后返回一个resolve状态的promise,否则等到执行完next传进来的函数再返回一个resolve状态的promise
try {
// 返回一个promise,并且执行fn函数,将context参数传入fn作为fn的ctx,将dispatch.bind(null, i + 1)传入fn作为fn的next
//这里将一个绑定了参数i的dispatch传入中间件,所以next并不是我们要执行的下一个中间件,而是next函数中可以访问到middleware也就是所有的中间件,只是因为i被bind绑定了,所以感觉像是下一个中间件函数
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
// 用来捕获中间件执行过程中的错误
return Promise.reject(err)
}
//这里通过Promise.resolve来执行中间件只要是为了支持async/await,不然你这样写代码岂不是就报错了吗 await next()
}
最后你可能要问洋葱圈模型在哪里?自己仔细想想吧