内容简介:最近在试着把自己写的从Koa 的 application.js 中找到中间件部分的代码,可以看出,use 传入的中间件被放入一个middleware 缓存队列中,这个队列会经由而进入到
最近在试着把自己写的 koa-vuessr-middleware 应用在旧项目中时,因为旧项目Koa 版本为1.2,对中间件的支持不一致,在转化之后好奇地读了一下源码,整理了一下对Koa 中next 在两个版本中的意义及相互转换的理解
正文
1.x 中的next
从Koa 的 application.js 中找到中间件部分的代码,可以看出,use 传入的中间件被放入一个middleware 缓存队列中,这个队列会经由 koa-compose
进行串联
app.use = function(fn){ // ... this.middleware.push(fn); return this; }; // ... app.callback = function(){ // ... var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware)); // ... }; 复制代码
而进入到 koa-compose
中,可以看到compose 的实现很有意思(无论是在1.x 还是在2.x 中,2.x 可以看下面的)
function compose(middleware){ return function *(next){ if (!next) next = noop(); var i = middleware.length; while (i--) { next = middleware[i].call(this, next); } return yield *next; } } // 返回一个generator 函数 function *noop(){} 复制代码
从代码中可以看出来,其实 next
本身就是一个generator, 然后在递减的过程中,实现了中间件的先进后出。换句话说,就是中间件会从最后一个开始,一直往前执行,而后一个中间件得到 generator
对象(即 next
)会作为参数传给前一个中间件,而最后一个中间件的参数next 是由 noop
函数生成的一个generator
但是如果在generator 函数内部去调用另一个generator函数,默认情况下是没有效果的,compose 用了一个 yield *
表达式,关于 yield *
,可以看看阮一峰老师的讲解;
2.x 中的next
Koa 到了2.x,代码越发精简了,基本的思想还是一样的,依然是缓存中间件并使用compose 进行串联,只是中间件参数从一个 next
变成了 (ctx, next)
,且中间件再不是generator函数而是一个 async/await 函数了
use(fn) { // ... this.middleware.push(fn); return this; } // ... callback() { const fn = compose(this.middleware); // .. } 复制代码
同时, compose 的实现也变了,相较于1.x 显得复杂了一些,用了四层return,将关注点放在 dispatch
函数上:
function compose (middleware) { 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) } } } } 复制代码
神来之笔在于 Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
这一句,乍看一下有点难懂,实际上 fn(context, dispatch.bind(null, i + 1))
就相当于一个中间件,然后递归调用下一个中间件,我们从 dispatch(0)
开始将它展开:
// 执行第一个中间件 p1-1 Promise.resolve(function(context, next){ console.log('executing first mw'); // 执行第二个中间件 p2-1 await Promise.resolve(function(context, next){ console.log('executing second mw'); // 执行第三个中间件 p3-1 await Promise(function(context, next){ console.log('executing third mw'); await next() // 回过来执行 p3-2 console.log('executing third mw2'); }()); // 回过来执行 p2-2 console.log('executing second mw2'); }) // 回过来执行 p1-2 console.log('executing first mw2'); }()); 复制代码
执行顺序可以理解为以下的样子:
// 执行第一个中间件 p1-1 first = (ctx, next) => { console.log('executing first mw'); next(); // next() 即执行了第二个中间件 p2-1 second = (ctx, next) => { console.log('executing second mw'); next(); // next() 即执行了第三个中间件 p3-1 third = (ctx, next) => { console.log('executing third mw'); next(); // 没有下一个中间件了, 开始执行剩余代码 // 回过来执行 p3-2 console.log('executing third mw2'); } // 回过来执行 p2-2 console.log('executing second mw2'); } // 回过来执行 p1-2 console.log('executing first mw2'); } 复制代码
从上面我们也能看出来,如果我们在中间件中没有执行 await next()
的话,就无法进入下一个中间件,导致运行停住。在2.x 中, next
不再是generator,而是以包裹在 Promise.resolve
中的普通函数等待await 执行。
相互转换
Koa 的中间件在1.x 和2.x 中是不完全兼容的,需要使用 koa-convert
进行兼容,它不但提供了从1.x 的generator转换到2.x 的Promise 的能力,还提供了从2.x 回退到1.x 的兼容方法,来看下核心源码:
function convert (mw) { // ... const converted = function (ctx, next) { return co.call(ctx, mw.call(ctx, createGenerator(next))) } // ... } function * createGenerator (next) { return yield next() } 复制代码
以上是从1.x 转化为2.x 的过程,先将next 转化为generator,然后使用 mw.call(ctx, createGenerator(next))
返回一个遍历器(此处传入的是 * (next) => ()
因此mw 为generator 函数),最后使用 co.call
去执行generator 函数返回一个 Promise
,关于 co
的解读可以参考Koa 生成器函数探寻;
接下来我们来看看回退到1.x 版本的方法
convert.back = function (mw) { // ... const converted = function * (next) { let ctx = this yield Promise.resolve(mw(ctx, function () { // .. return co.call(ctx, next) })) } // ... } 复制代码
在这里,由于2.x 的上下文对象ctx 等同于1.x 中的上下文对象,即this,在返回的generator 中将this 作为上下文对象传入2.x 版本中间件的ctx 参数中,并将中间件Promise化并使用yield 返回
总结
总的来说,在 1.x 和2.x 中,next 都充当了一个串联各个中间件的角色,其设计思路和实现无不展现了作者的功底之强,十分值得回味学习
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The CS Detective: An Algorithmic Tale of Crime, Conspiracy, and
Jeremy Kubica / No Starch Press / 2016-8-15 / USD 13.74
Meet Frank Runtime. Disgraced ex-detective. Hard-boiled private eye. Search expert.When a robbery hits police headquarters, it's up to Frank Runtime and his extensive search skills to catch the culpri......一起来看看 《The CS Detective: An Algorithmic Tale of Crime, Conspiracy, and 》 这本书的介绍吧!