前言

最近在试着把自己写的 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 = -1return dispatch(0)function dispatch (i) {if (i <= index) return Promise.reject(new Error('next() called multiple times'))index = ilet fn = middleware[i]if (i === middleware.length) fn = nextif (!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-1await Promise.resolve(function(context, next){console.log('executing second mw');// 执行第三个中间件 p3-1await Promise(function(context, next){console.log('executing third mw');await next()// 回过来执行 p3-2console.log('executing third mw2');}());// 回过来执行 p2-2console.log('executing second mw2');})// 回过来执行 p1-2console.log('executing first mw2');
}());
复制代码

执行顺序可以理解为以下的样子:

// 执行第一个中间件 p1-1
first = (ctx, next) => {console.log('executing first mw');next();// next() 即执行了第二个中间件 p2-1second = (ctx, next) => {console.log('executing second mw');next();// next() 即执行了第三个中间件 p3-1third = (ctx, next) => {console.log('executing third mw');next(); // 没有下一个中间件了, 开始执行剩余代码// 回过来执行 p3-2console.log('executing third mw2');}// 回过来执行 p2-2console.log('executing second mw2');}// 回过来执行 p1-2console.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 = thisyield 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 都充当了一个串联各个中间件的角色,其设计思路和实现无不展现了作者的功底之强,十分值得回味学习

谈谈Koa 中的next相关推荐

  1. koa中使用cookie 和session

    在koa中使用cookie app.keys = ['im a newer secret'] //设置签名的 Cookie 密钥. // 设置cookie app.use(async ctx => ...

  2. html js脚本限制 正则,简单谈谈JS中的正则表达式

    1.正则表达式包括两部分 ①定义正则表达式的规则: ②正则表达式的模式(i/g/m): 2.声明正则表达式 ① 字面量声明: var reg = /表达式规则/表达式模式: eg:var reg = ...

  3. koa --- nunjucks在Koa中的使用、中间件的配置

    Nunjucks在Koa中的应用 app.js const koa = require('koa'); const app = new koa(); const router = require('. ...

  4. Linux存储保护,谈谈Linux中的存储保护

    谈谈Linux中的存储保护 以下讨论的内容是以i386平台为基础的 Linux将4G的地址划分为用户空间和内核空间两部分.在Linux内核的低版本中(2.0.X),通常0-3G为用户空间,3G-4G为 ...

  5. Koa 中实现 chunked 数据传输

    有关于 Transfer-Encoding:chunked 类型的响应,参见之前的文章HTTP 响应的分块传输.这里看 Koa 中如何实现. Koa 中请求返回的处理 虽然官方文档有描述说明不建议直接 ...

  6. 谈谈JAVA中的安全发布

    谈谈JAVA中的安全发布 昨天看到一篇文章阐述技术类资料的"等级",看完之后很有共鸣.再加上最近在工作中越发觉得线程安全性的重要性和难以捉摸,又掏出了<Java并发编程实战& ...

  7. Spark精华问答 | 谈谈spark中的宽窄依赖

    总的来说,Spark采用更先进的架构,使得灵活性.易用性.性能等方面都比Hadoop更有优势,有取代Hadoop的趋势,但其稳定性有待进一步提高.我总结,具体表现在如下几个方面. 1 Q:Spark ...

  8. 谈谈C#中的三个关键词new , virtual , override

    谈谈C#中的三个关键词new , virtual , override C#支持单继承,说到继承就不得不说new,virtual和override这三个关键词,灵活正确的使用这三个关键词,可以使程序结 ...

  9. KOA中的ejs的基本使用

    ejs 服务端模板引擎 ejs文件也可以使用script标签 来写js内容 在koa中使用ejs 先导入和注册 const views = require('koa-views') app.use(v ...

最新文章

  1. 2020 java swing jtable 合并_java学生管理系统(界面版)
  2. 阿里云开启多媒体搜索新时代,发布全域精准图像搜索
  3. 解决TypeError: Tensor is unhashable if Tensor equality is enabled. Instead, use tensor.experimental_re
  4. 安装 pptpd 服务
  5. 【CodeForces - 349C】Mafia(思维模拟,优秀的二分)
  6. java初入多线程6
  7. Halcon 学习总结——错误处理方法
  8. mysql上面waring删掉吗_MySQL经典练习题:数据插入,更新,删除
  9. 老罗Android开发视频教程 (android解析xml文件 )3集集合
  10. JavaScript 页面刷新方法
  11. brook客户端android下哪个,‎App Store 上的“Brook Steakburguer”
  12. 新世达380修改服务器密码,新时达AS380利用小键盘调试说明
  13. Fisher's exact test( 费希尔精确检验)
  14. ECharts动态图表展示
  15. 设计要用计算机吗,作为设计师 你需要这样的笔记本电脑
  16. 阻塞(blockage)设置优化——Hard,Soft,Partial
  17. 《Spring事务传播行为详解》经典例子 看完这篇,别的不用看了
  18. 图像恢复(加噪与去噪)
  19. 黑龙江省计算机一级考试科目,黑龙江省2021年3月全国计算机等级考试时间和科目调整...
  20. Hibernate入门-03

热门文章

  1. 很抱歉,这场大会我们没法卖票给你了
  2. 反杀人类、拯救机器狗,被虐士兵机器人化身终结者!这是“波士屯动力”的最新力作...
  3. 加速产业AI化!浪潮提出”元脑“生态计划,要用计算力+生态成就行业AI大脑...
  4. 用自然语言从GitHub搜代码,跳过论坛提问环节,来自Facebook新研究
  5. jquery-1.4.4.min.js无法解析json中result.data问题
  6. java8-06-自定义Collector-JoinCollector
  7. python matplot 绘图
  8. python中关于操作时间的方法(一):使用time模块
  9. Android手机刷recovery
  10. CentOS 漏洞修补