内容简介:最近在试着把自己写的从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 都充当了一个串联各个中间件的角色,其设计思路和实现无不展现了作者的功底之强,十分值得回味学习
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First Web Design
Ethan Watrall、Jeff Siarto / O’Reilly Media, Inc. / 2009-01-02 / USD 49.99
Want to know how to make your pages look beautiful, communicate your message effectively, guide visitors through your website with ease, and get everything approved by the accessibility and usability ......一起来看看 《Head First Web Design》 这本书的介绍吧!