深入探析koa之中间件流程控制篇
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的中间件执行机制,可以用下图的洋葱模型来形容:

通过这种执行流程,开发者可以非常方便的开发一些中间件,并且非常容易的整合到实际业务流程中。那么,这样的流程又是如何实现和控制的呢?
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文件就是本篇的事例源码。
另外欢迎多多关注我的个人博客哦_ 会不定期更新我的技术文章~
深入探析koa之中间件流程控制篇的更多相关文章
- 深入探析koa之异步回调处理篇
在上一篇中我们梳理了koa当中中间件的洋葱模型执行原理,并实现了一个可以让洋葱模型自动跑起来的流程管理函数.这一篇,我们再来研究一下koa当中异步回调同步化写法的原理,同样的,我们也会实现一个管理函数 ...
- 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded
在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...
- 使用yield进行异步流程控制
现状 目前我们对异步回调的解决方案有这么几种:回调,deferred/promise和事件触发.回调的方式自不必说,需要硬编码调用,而且有可能会出现复杂的嵌套关系,造成"回调黑洞" ...
- node核心:异步流程控制
Node.js的异步是整个学习Node.js过程中重中之重. 1)异步流程控制学习重点 2)Api写法:Error-first Callback 和 EventEmitter 3)中流砥柱:Promi ...
- 中文分词工具探析(一):ICTCLAS (NLPIR)
1. 前言 ICTCLAS是张华平在2000年推出的中文分词系统,于2009年更名为NLPIR.ICTCLAS是中文分词界元老级工具了,作者开放出了free版本的源代码(1.0整理版本在此). 作者在 ...
- node基础13:异步流程控制
1.流程控制 因为在node中大部分的api都是异步的,比如说读取文件,如果采用回调函数的形式,很容易造成地狱回调,代码非常不容易进行维护. 因此,为了解决这个问题,有大神写了async这个中间件.极 ...
- 开源中文分词工具探析(四):THULAC
THULAC是一款相当不错的中文分词工具,准确率高.分词速度蛮快的:并且在工程上做了很多优化,比如:用DAT存储训练特征(压缩训练模型),加入了标点符号的特征(提高分词准确率)等. 1. 前言 THU ...
- Erlang调度器细节探析
Erlang调度器细节探析 Erlang的很多基础特性使得它成为一个软实时的平台.其中包括垃圾回收机制,详细内容可以参见我的上一篇文章Erlang Garbage Collection Details ...
- 开源中文分词工具探析(五):Stanford CoreNLP
CoreNLP是由斯坦福大学开源的一套Java NLP工具,提供诸如:词性标注(part-of-speech (POS) tagger).命名实体识别(named entity recognizer ...
随机推荐
- strutx.xml中配置文件的讲解
Struts2框架的核心就是struts.xml文件了,该文件主要负责管理Struts的2的业务控制组件的核心内容.为了避免struts.xml的文件国 语庞大和臃肿,我们可以通过把一个struts. ...
- 用adb pull命令从android系统中读取文件失败的原因及解决办法
问题:使用adb pull命令从android系统中读取文件失败.显示:Permission denied 原因:是由于文件权限原因引起. 使用ls -l命令查看android系统中的 ...
- Fedora 下 安装 chrome
一.下载安装包,安装 1.去google 下载安装包 2.终端下 运行命令: rpm -ivh google-chrome-stable_current_i386.rpm 3. 出现如下 ...
- JDK1.6官方下载_JDK6官方下载_JDK1.6API(chm)下载_JDK6APICHM中文参
JDK1.6官方下载_JDK6官方下载地址:http://www.java.net/download/jdk6/6u10/promoted/b32/binaries/jdk-6u10-rc2-bin- ...
- Spring 3.1 Environment Profiles--转载
原文地址:http://gordondickens.com/wordpress/2012/06/12/spring-3-1-environment-profiles/ Profiles Spring ...
- Java字符流和字节流对文件操作
记得当初自己刚开始学习Java的时候,对Java的IO流这一块特别不明白,所以写了这篇随笔希望能对刚开始学习Java的人有所帮助,也方便以后自己查询.Java的IO流分为字符流(Reader,Writ ...
- The method load(Class, Serializable) in the type HibernateTemplate is not applicable for the arguments (Class, int)
引入别人的项目发现利用HibernateTemplate的load的方法报错了.错误提示为: The method load(Class, Serializable) in the type Hibe ...
- java中访问mysql数据库中的表结构信息
package cn.hncu.meta; import java.sql.Connection;import java.sql.DatabaseMetaData;import java.sql.Re ...
- ASP.NET MVC and jqGrid 学习笔记 6-增删改操作
程序结构: Member.cs CRUD.cshtml CRUD.js HomeController 一.Model public class Member { [Key] public int No ...
- 乐在其中设计模式(C#) - 代理模式(Proxy Pattern)【转】
介绍 为其他对象提供一个代理以控制对这个对象的访问. 示例 有一个Message实体类,某对象对它的操作有Insert()和Get()方法,用一个代理来控制对这个对象的访问. MessageModel ...