详解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 相关文章推荐
JavaScript 事件属性绑定带参数的函数
Mar 13 Javascript
Jquery 绑定时间实现代码
May 03 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(一)让静态人物动起来
Jan 23 Javascript
jQuery实现鼠标经过事件的延时处理效果
Aug 20 Javascript
JavaScript实现带播放列表的音乐播放器实例分享
Mar 07 Javascript
41个Web开发者必须收藏的JavaScript实用技巧
Jul 22 Javascript
Vue2.x中的父组件传递数据至子组件的方法
May 01 Javascript
JS实现获取汉字首字母拼音、全拼音及混拼音的方法
Nov 14 Javascript
微信小程序如何获取用户信息
Jan 26 Javascript
浅谈Koa2框架利用CORS完成跨域ajax请求
Mar 06 Javascript
Koa从零搭建到Api实现项目的搭建方法
Jul 30 Javascript
Vue基本指令实例图文讲解
Feb 25 Vue.js
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
2020年4月放送!《Princess Connect Re:Dive》制作组 & 角色声优公开!
2020/03/06 日漫
php实现无限级分类实现代码(递归方法)
2011/01/01 PHP
PHP批量上传图片的具体实现方法介绍.
2014/02/26 PHP
浅谈php安全性需要注意的几点事项
2014/07/17 PHP
php检查字符串中是否有外链的方法
2015/07/29 PHP
srcElement表格样式
2006/09/03 Javascript
一个简单的Ext.XTemplate的实例代码
2012/03/18 Javascript
js获取当前地址 JS获取当前URL的示例代码
2014/02/26 Javascript
JS中捕获console.log()输出的方法
2015/04/16 Javascript
基于RequireJS和JQuery的模块化编程日常问题解析
2016/04/14 Javascript
jQuery使用经验小技巧(推荐)
2016/05/31 Javascript
BootStrap Progressbar 实现大文件上传的进度条的实例代码
2016/06/27 Javascript
AngularJS实现与Java Web服务器交互操作示例【附demo源码下载】
2016/11/02 Javascript
Bootstrap table使用方法详细介绍
2016/12/09 Javascript
学习LayUI时自研的表单参数校验框架案例分析
2019/07/29 Javascript
keep-alive不能缓存多层级路由菜单问题解决
2020/03/10 Javascript
Python文件去除注释的方法
2015/05/25 Python
python3 pillow生成简单验证码图片的示例
2017/09/19 Python
Sanic框架蓝图用法实例分析
2018/07/17 Python
Python 处理图片像素点的实例
2019/01/08 Python
python读取ini配置文件过程示范
2019/12/23 Python
Holland & Barrett爱尔兰:英国领先的健康零售商
2019/03/31 全球购物
Kendra Scott官网:美国领先的时尚配饰品牌
2020/10/22 全球购物
生产部统计员岗位职责
2014/01/05 职场文书
《囚绿记》教学反思
2014/03/01 职场文书
农业生产宣传标语
2014/10/08 职场文书
2014离婚协议书范文(3篇)
2014/11/29 职场文书
论文致谢词范文
2015/05/14 职场文书
团委副书记工作总结
2015/08/14 职场文书
关于公司年会的开幕词
2016/03/04 职场文书
2016年小学“公民道德宣传日”活动总结
2016/04/01 职场文书
从np.random.normal()到正态分布的拟合操作
2021/06/02 Python
vue中利用mqtt服务端实现即时通讯的步骤记录
2021/07/01 Vue.js
漫画「处刑少女的生存之道」第3卷封面公开
2022/03/21 日漫
Python通过loop.run_in_executor执行同步代码 同步变为异步
2022/04/11 Python
Mysql 文件配置解析介绍
2022/05/06 MySQL