深入解析koa之中间件流程控制


Posted in Javascript onJune 17, 2019

前言

koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁。在使用koa的过程中,其实一直比较好奇koa内部的实现机理。最近终于有空,比较深入的研究了一下koa一些原理,在这里会写一系列文章来记录一下我的学习心得和理解。

在我看来,koa最核心的函数是大名鼎鼎的co,koa正是基于这个函数实现了异步回调同步化,以及中间件流程控制。当然在这篇文章中我并不会去分析co源码,我打算在整个系列文章中,一步一步讲解如何实现koa中间件的流程控制原理,koa的异步回调同步写法实现原理,最后在理解这些的基础上,实现一个简单的类似co的函数。

本篇首先只谈一谈koa的中间件流程控制原理。

1. koa中间件执行流程

关于koa中间件如何执行,官网上有一个非常经典的例子,有兴趣的可以去看看,不过这里,我想把它修改的更简单一点:

var koa = require('koa');
var app = koa();
app.use(function*(next) {
console.log('begin middleware 1');
yield next;
console.log('end middleware 1');
});
app.use(function*(next) {
console.log('begin middleware 2');
yield next;
console.log('end middleware 2');
});
app.use(function*() {
console.log('middleware 3');
});
app.listen(3000);

运行这个例子,然后使用curl工具,运行:

curl http://localhost:3000

可以看到,运行之后,会输出:

begin middleware 1
begin middleware 2
middleware 3
end middleware 2
end middleware 1

这个例子非常形象的代表了koa的中间件执行机制,可以用下图的洋葱模型来形容:

深入解析koa之中间件流程控制

通过这种执行流程,开发者可以非常方便的开发一些中间件,并且非常容易的整合到实际业务流程中。那么,这样的流程又是如何实现和控制的呢?

2. koa中的generator和compose

简单来说,洋葱模型的执行流程是通过es6中的generator来实现的。不熟悉generator的同学可以去看看其特性,其中一个就是generator函数可以像打断点一样从函数某个地方跳出,之后还可以再回来继续执行。下面一个例子可以说明这种特性:

var gen=function*(){
console.log('begin!');
//yield语句,在这里跳出,将控制权交给anotherfunc函数。
yield anotherfunc;
//下次回来时候从这里开始执行
console.log('end!');
}
var anotherfunc(){
console.log('this is another function!');
}
var g=gen();
var another=g.next(); //'begin!'
//another是一个对象,其中value成员就是返回的anotherfunc函数
another.value(); //'this is another function!'
g.next(); //'end!';

从这个简单例子中,可以看出洋葱模型最基本的一个雏形,即yield前后的语句最先和最后执行,yield中间的代码在中心执行。

现在设想一下,如果yield后面跟的函数本身就又是一个generator,会怎么样呢?其实就是从上面例子里面做一个引申:

var gen1=function*(){
console.log('begin!');
yield g2;
console.log('end!');
}
var gen2=function*(){
console.log('begin 2');
yield anotherfunc;
console.log('end 2');
}
var anotherfunc(){
console.log('this is another function!');
}
var g=gen();
var g2=gen2();
var another1=g.next(); //'begin!';
var another2=another1.value.next(); //'begin 2';
another2.value(); //'this is another function!';
another1.value.next(); //'end 2';
g.next(); //'end!';

可以看出,基本上是用上面的例子,再加一个嵌套而已,原理是一样的。

而在koa中,每个中间件generator都有一个next参数。在我们这个例子中,g2就可以看成是g函数的next参数。事实上,koa也确实是这样做的,当使用app.use()挂载了所有中间件之后,koa有一个koa-compose模块,用于将所有generator中间件串联起来,基本上就是将后一个generator赋给前一个generator的next参数。koa-compose的源码非常简单短小,下面是我自己实现的一个:

function compose(middlewares) {
return function(next) {
var i = middlewares.length;
var next = function*() {}();
while (i--) {
next = middlewares[i].call(this, next);
}
return next;
}
}

使用我们自己写的compose对上面一个例子改造,是的其更接近koa的形式:

function compose(middlewares) {
return function(next) {
var i = middlewares.length;
var next = function*() {}();
while (i--) {
next = middlewares[i].call(this, next);
}
return next;
}
}
var gen1=function*(next){
console.log('begin!');
yield next;
console.log('end!');
}
var gen2=function*(next){
console.log('begin 2');
yield next;
console.log('end 2');
}
var gen3=function*(next){
console.log('this is another function!');
}
var bundle=compose([gen1,gen2,gen3]);
var g=bundle();
var another1=g.next(); //'begin!';
var another2=another1.value.next(); //'begin 2';
another2.value.next(); //'this is another function!';
another1.value.next(); //'end 2';
g.next(); //'end!';

怎么样?是不是有一点koa中间件写法的感觉了呢?但是目前,我们还是一步一步手动的在执行我们这个洋葱模型,能否写一个函数,自动的来执行我们这个模型呢?

3. 让洋葱模型自动跑起来:一个run函数的编写

上面例子中,最后的代码我们可以看出一个规律,基本就是外层的generator调用next方法把控制权交给内层,内层再继续调用next把方法交给更里面的一层。整个流程可以用一个函数嵌套的写法写出来。话不多说,直接上代码:

function run(gen) {
var g;
if (typeof gen.next === 'function') {
g = gen;
} else {
g = gen();
}
function next() {
var tmp = g.next();
//如果tmp.done为true,那么证明generator执行结束,返回。
if (tmp.done) {
return;
} else if (typeof g.next === 'function') {
run(tmp.value);
next();
}
}
next();
}
function compose(middlewares) {
return function(next) {
var i = middlewares.length;
var next = function*() {}();
while (i--) {
next = middlewares[i].call(this, next);
}
return next;
}
}
var gen1 = function*(next) {
console.log('begin!');
yield next;
console.log('end!');
}
var gen2 = function*(next) {
console.log('begin 2');
yield next;
console.log('end 2');
}
var gen3 = function*(next) {
console.log('this is another function!');
}
var bundle = compose([gen1, gen2, gen3]);
run(bundle);

run函数接受一个generator,其内部执行其实就是我们上一个例子的精简,使用递归的方法执行。运行这个例子,可以看到结果和我们上一个例子相同。

到此为止,我们就基本讲清楚了koa中的中间件洋葱模型是如何自动执行的。事实上,koa中使用的co函数,一部分功能就是实现我们这里编写的run函数的功能。

值得注意的是,这篇文章只注重分析中间件执行流程的实现,暂时并没有考虑异步回调同步化原理。下一篇文章中,我将带大家继续探析koa中异步回调同步化写法的机理。

这篇文章的代码可以在github上面找到:https://github.com/mly-zju/async-js-demo,其中process_control.js文件就是本篇的事例源码。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
HTML颜色选择器实现代码
Nov 23 Javascript
js 编程笔记 无名函数
Jun 28 Javascript
node.js使用npm 安装插件时提示install Error: ENOENT报错的解决方法
Nov 20 Javascript
JavaScript中textRange对象使用方法小结
Mar 24 Javascript
jQuery实现的五子棋游戏实例
Jun 13 Javascript
jQuery实现径向动画菜单效果
Jul 17 Javascript
JavaScript中Textarea滚动条不能拖动的解决方法
Dec 15 Javascript
Javascript点击按钮随机改变数字与其颜色
Sep 01 Javascript
JavaScript用JSONP跨域请求数据实例详解
Jan 06 Javascript
jquery平滑滚动到顶部插件使用详解
May 08 jQuery
ES6中Array.find()和findIndex()函数的用法详解
Sep 16 Javascript
react项目从新建到部署的实现示例
Feb 19 Javascript
深入解读Node.js中的koa源码
Jun 17 #Javascript
学习RxJS之JavaScript框架Cycle.js
Jun 17 #Javascript
javascript系统时间设置操作示例
Jun 17 #Javascript
深入学习TypeScript 、React、 Redux和Ant-Design的最佳实践
Jun 17 #Javascript
Vue程序调试的方法
Jun 17 #Javascript
Vue拖拽组件列表实现动态页面配置功能
Jun 17 #Javascript
javascript实现日历效果
Jun 17 #Javascript
You might like
骨王战斗力在公会成员中排不进前五,却当选了会长,原因竟是这样
2020/03/02 日漫
php curl 伪造IP来源的实例代码
2012/11/01 PHP
PHP自定session保存路径及删除、注销与写入的方法
2014/11/18 PHP
重构Javascript代码示例(重构前后对比)
2013/01/23 Javascript
Nodejs使用mysql模块之获得更新和删除影响的行数的方法
2014/03/18 NodeJs
Enter回车切换输入焦点实现思路与代码兼容各大浏览器
2014/09/01 Javascript
JavaScript继承模式粗探
2016/01/12 Javascript
深入理解Javascript中的自执行匿名函数
2016/06/03 Javascript
Highcharts入门之基本属性
2016/08/02 Javascript
ionic实现滑动的三种方式
2016/08/27 Javascript
详解Vue webapp项目通过HBulider打包原生APP
2018/06/29 Javascript
Vue 事件处理操作实例详解
2019/03/05 Javascript
小程序中this.setData的使用和注意事项
2019/08/28 Javascript
javascript实现切割轮播效果
2019/11/28 Javascript
JS使用setInterval计时器实现挑战10秒
2020/11/08 Javascript
在Linux下调试Python代码的各种方法
2015/04/17 Python
Pycharm学习教程(3) 代码运行调试
2017/05/03 Python
Python button选取本地图片并显示的实例
2019/06/13 Python
pyqt5之将textBrowser的内容写入txt文档的方法
2019/06/21 Python
Flask框架学习笔记之使用Flask实现表单开发详解
2019/08/12 Python
Python动态声明变量赋值代码实例
2019/12/30 Python
Python 剪绳子的多种思路实现(动态规划和贪心)
2020/02/24 Python
基于Keras 循环训练模型跑数据时内存泄漏的解决方式
2020/06/11 Python
CSS3 flex布局之快速实现BorderLayout布局
2015/12/03 HTML / CSS
H5调用相机拍照并压缩图片的实例代码
2017/07/20 HTML / CSS
eBay澳大利亚站:eBay.com.au
2018/02/02 全球购物
优秀毕业生求职推荐信范文
2013/11/21 职场文书
优秀员工自荐书范文
2013/12/08 职场文书
编辑求职信样本
2013/12/16 职场文书
2015年社区综治工作总结
2015/04/21 职场文书
小学生读书笔记
2015/07/01 职场文书
会计岗位工作总结
2015/08/12 职场文书
教师旷工检讨书
2015/08/15 职场文书
2016年度农村党员干部主题教育活动总结
2016/04/06 职场文书
一文读懂navicat for mysql基础知识
2021/05/31 MySQL
MySQL读取JSON转换的方式
2022/03/18 MySQL