详解express与koa中间件模式对比


Posted in Javascript onAugust 07, 2017

起因

最近在学习koa的使用, 由于koa是相当基础的web框架,所以一个完整的web应用所需要的东西大都以中间件的形式引入,比如koa-router, koa-view等。在koa的文档里有提到:koa的中间件模式与express的是不一样的,koa是洋葱型,express是直线型,至于为什么这样,网上很多文章并没有具体分析。或者简单的说是async/await的特性之类。先不说这种说法的对错,对于我来说这种说法还是太模糊了。所以我决定通过源码来分析二者中间件实现的原理以及用法的异同。

为了简单起见这里的express用connect代替(实现原理是一致的)

用法

二者都以官网(github)文档为准

connect

下面是官网的用法:

var connect = require('connect');
var http = require('http');

var app = connect();

// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());

// store session state in browser cookie
var cookieSession = require('cookie-session');
app.use(cookieSession({
 keys: ['secret1', 'secret2']
}));

// parse urlencoded request bodies into req.body
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));

// respond to all requests
app.use(function(req, res){
 res.end('Hello from Connect!\n');
});

//create node.js http server and listen on port
http.createServer(app).listen(3000);

根据文档我们可以看到,connect是提供简单的路由功能的:

app.use('/foo', function fooMiddleware(req, res, next) {
 // req.url starts with "/foo"
 next();
});
app.use('/bar', function barMiddleware(req, res, next) {
 // req.url starts with "/bar"
 next();
});

connect的中间件是线性的,next过后继续寻找下一个中间件,这种模式直觉上也很好理解,中间件就是一系列数组,通过路由匹配来寻找相应路由的处理方法也就是中间件。事实上connect也是这么实现的。

app.use 就是往中间件数组中塞入新的中间件。中间件的执行则依靠私有方法 app.handle 进行处理,express也是相同的道理。

koa

相对connect,koa的中间件模式就不那么直观了,借用网上的图表示:

详解express与koa中间件模式对比

也就是koa处理完中间件后还会回来走一趟,这就给了我们更加大的操作空间,来看看koa的官网实例:

const Koa = require('koa');
const app = new Koa();

// x-response-time

app.use(async (ctx, next) => {
 const start = Date.now();
 await next();
 const ms = Date.now() - start;
 ctx.set('X-Response-Time', `${ms}ms`);
});

// logger

app.use(async (ctx, next) => {
 const start = Date.now();
 await next();
 const ms = Date.now() - start;
 console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

// response

app.use(async ctx => {
 ctx.body = 'Hello World';
});

app.listen(3000);

很明显,当koa处理中间件遇到await next()的时候会暂停当前中间件进而处理下一个中间件,最后再回过头来继续处理剩下的任务,虽然说起来很复杂,但是直觉上我们会有一种隐隐熟悉的感觉:不就是回调函数吗。这里暂且不说具体实现方法,但是确实就是回调函数。跟async/await的特性并无任何关系。

源码简析

connect与koa中间件模式区别的核心就在于next的实现,让我们简单看下二者next的实现。

connect

connect的源码相当少加上注释也就200来行,看起来也很清楚,connect中间件处理在于proto.handle这个私有方法,同样next也是在这里实现的

// 中间件索引
var index = 0
function next(err) {


 // 递增
 var layer = stack[index++];

 // 交由其他部分处理
 if (!layer) {
  defer(done, err);
  return;
 }

 // route data
 var path = parseUrl(req).pathname || '/';
 var route = layer.route;

 // 递归
 // skip this layer if the route doesn't match
 if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
  return next(err);
 }

 // call the layer handle
 call(layer.handle, route, err, req, res, next);
 }

删掉混淆的代码后 我们可以看到next实现也很简洁。一个递归调用顺序寻找中间件。不断的调用next。代码相当简单但是思路却很值得学习。

其中 done 是第三方处理方法。其他处理sub app以及路由的部分都删除了。不是重点

koa

koa将next的实现抽离成了一个单独的包,代码更加简单,但是实现了一个貌似更加复杂的功能

function compose (middleware) {
 return function (context, next) {
 // last called middleware #
 let index = -1
 return dispatch(0)
 function dispatch (i) {
  index = i
  try {
  return Promise.resolve(fn(context, function next () {
   return dispatch(i + 1)
  }))
  } catch (err) {
  return Promise.reject(err)
  }
 }
 }
}

看着上面处理过的的代码 有些同学可能还是会不明觉厉。

那么我们继续处理一下:

function compose (middleware) {

 return function (context, next) {
 // last called middleware #
 let index = -1
 return dispatch(0)
 function dispatch (i) {
  index = i
  let fn = middleware[i]
  if (i === middleware.length) {
  fn = next
  }
  if (!fn) return
  return fn(context, function next () {
  return dispatch(i + 1)
  })
 }
 }
}

这样一来 程序更加简单了 跟async/await也没有任何关系了,让我们看下结果好了

var ms = [
 function foo (ctx, next) {
 console.log('foo1')
 next()
 console.log('foo2')
 },
 function bar (ctx, next) {
 console.log('bar1')
 next()
 console.log('bar2')
 },
 function qux (ctx, next) {
 console.log('qux1')
 next()
 console.log('qux2')
 }
]

compose(ms)()

执行上面的程序我们可以发现依次输出:

foo1
bar1
qux1
qux2
bar2
foo2

同样是所谓koa的洋葱模型,到这里我们就可以得出这样一个结论:koa的中间件模型跟async或者generator并没有实际联系,只是koa强调async优先。所谓中间件暂停也只是回调函数的原因(在我看来promise.then与回调其实没有什么区别,甚至async/await也是回调的一种形式)。

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

Javascript 相关文章推荐
点击显示指定元素隐藏其他同辈元素的方法
Feb 19 Javascript
IE及IE6浏览器中判断JS文件加载成功失败的方法
Feb 18 Javascript
javascript实现youku的视频代码自适应宽度
May 25 Javascript
将form表单通过ajax实现无刷新提交的简单实例
Oct 12 Javascript
JS取数字小数点后两位或n位的简单方法
Oct 24 Javascript
jQuery表单插件ajaxForm实例详解
Jan 17 Javascript
ES6新特性四:变量的解构赋值实例
Apr 21 Javascript
jQuery插件FusionCharts绘制的2D条状图效果【附demo源码】
May 13 jQuery
jQuery查找dom的几种方法效率详解
May 17 jQuery
使用JavaScript实现链表的数据结构的代码
Aug 02 Javascript
JS基于ES6新特性async await进行异步处理操作示例
Feb 02 Javascript
Vue 实现手动刷新组件的方法
Feb 19 Javascript
JS实现简单短信验证码界面
Aug 07 #Javascript
ReactNative Image组件使用详解
Aug 07 #Javascript
JS实现移动端判断上拉和下滑功能
Aug 07 #Javascript
在iframe中使bootstrap的模态框在父页面弹出问题
Aug 07 #Javascript
JS判断微信扫码的方法
Aug 07 #Javascript
AngularJs每天学习之总体介绍
Aug 07 #Javascript
关于webpack2和模块打包的新手指南(小结)
Aug 07 #Javascript
You might like
PHP中用header图片地址 简单隐藏图片源地址
2008/04/09 PHP
PHP Warning: PHP Startup: Unable to load dynamic library \ D:/php5/ext/php_mysqli.dll\
2012/06/17 PHP
php异常处理使用示例
2014/02/25 PHP
C#使用PHP服务端的Web Service通信实例
2014/04/08 PHP
PHP面向对象程序设计实例分析
2016/01/26 PHP
zend framework重定向方法小结
2016/05/28 PHP
php  单例模式详细介绍及实现源码
2016/11/05 PHP
php中__toString()方法用法示例
2016/12/07 PHP
PHP排序算法之简单选择排序(Simple Selection Sort)实例分析
2018/04/20 PHP
javascript编程起步(第五课)
2007/01/10 Javascript
如何制作浮动广告 JavaScript制作浮动广告代码
2012/12/30 Javascript
9个让JavaScript调试更简单的Console命令
2016/11/14 Javascript
JavaScript中定时控制Throttle、Debounce和Immediate详解
2016/11/17 Javascript
ES6正则的扩展实例详解
2017/04/25 Javascript
实例详解BootStrap的动态模态框及静态模态框
2018/08/13 Javascript
js Array.slice的8种不同用法示例
2019/07/10 Javascript
javascript合并两个数组最简单的实现方法
2019/09/14 Javascript
微信小程序复选框实现多选一功能过程解析
2020/02/14 Javascript
[48:51]完美世界DOTA2联赛PWL S2 Magma vs InkIce 第一场 11.28
2020/12/02 DOTA
Python和php通信乱码问题解决方法
2014/04/15 Python
Python微医挂号网医生数据抓取
2019/01/24 Python
在Pandas中DataFrame数据合并,连接(concat,merge,join)的实例
2019/01/29 Python
pandas 空数据处理方法详解
2019/11/02 Python
基于python实现把图片转换成素描
2019/11/13 Python
台湾网购生鲜第一品牌:i3Fresh爱上新鲜
2017/10/26 全球购物
国贸专业大学生职业生涯规划范文
2014/01/10 职场文书
环保建议书
2014/03/12 职场文书
签约仪式策划方案
2014/06/02 职场文书
2014领导班子正风肃纪思想汇报
2014/09/18 职场文书
2014年驾驶员工作总结
2014/11/18 职场文书
医德医风个人总结
2015/02/28 职场文书
2016年记者节感言
2015/12/08 职场文书
详解python字符串驻留技术
2021/05/21 Python
详解Python类和对象内容
2021/06/22 Python
用Python编写简单的gRPC服务的详细过程
2021/07/04 Python
win11无法登录onedrive错误代码0x8004def7怎么办 ?
2022/04/05 数码科技