详解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 相关文章推荐
JScript中的"this"关键字使用方式补充材料
Mar 08 Javascript
jQuery实现的简单折叠菜单(折叠面板)效果代码
Sep 16 Javascript
javascript多物体运动实现方法分析
Jan 08 Javascript
jQuery获取访问者IP地址的方法(基于新浪API与QQ查询接口)
May 25 Javascript
jquery日历插件e-calendar升级版
Nov 10 Javascript
MUI实现上拉加载和下拉刷新效果
Jun 30 Javascript
jQuery+Ajax请求本地数据加载商品列表页并跳转详情页的实现方法
Jul 12 jQuery
vue2实现可复用的轮播图carousel组件详解
Nov 27 Javascript
JavaScript中的"=、==、==="区别讲解
Jan 22 Javascript
详解vuex持久化插件解决浏览器刷新数据消失问题
Apr 15 Javascript
手把手教你 CKEDITOR 4 实现Dialog 内嵌 IFrame操作详解
Jun 18 Javascript
vue中div禁止点击事件的实现
Apr 02 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
解析php中session的实现原理以及大网站应用应注意的问题
2013/06/17 PHP
PHP时间类完整实例(非常实用)
2015/12/25 PHP
利用php生成验证码
2017/02/23 PHP
javascript jQuery插件练习
2008/12/24 Javascript
Jquery操作Select 简单方便 一个js插件搞定
2009/11/12 Javascript
Extjs4 消息框去掉关闭按钮(类似Ext.Msg.alert)
2013/04/02 Javascript
JavaScript window.location对象
2014/11/14 Javascript
JS替换字符串中空格方法
2015/04/17 Javascript
基于JavaScript FileReader上传图片显示本地链接
2016/05/27 Javascript
JS正则匹配URL网址的方法(可匹配www,http开头的一切网址)
2017/01/06 Javascript
浅析Vue自定义组件的v-model
2017/11/26 Javascript
bootstrap table实现横向合并与纵向合并
2019/07/18 Javascript
写给新手同学的vuex快速上手指北小结
2020/04/14 Javascript
python实现备份目录的方法
2015/08/03 Python
Django使用paginator插件实现翻页功能的实例
2018/10/24 Python
Python第三方库face_recognition在windows上的安装过程
2019/05/03 Python
python Django的web开发实例(入门)
2019/07/31 Python
tensorflow没有output结点,存储成pb文件的例子
2020/01/04 Python
Django后台管理系统的图文使用教学
2020/01/20 Python
Spring http服务远程调用实现过程解析
2020/06/11 Python
详解Anaconda安装tensorflow报错问题解决方法
2020/11/01 Python
Python+OpenCV检测灯光亮点的实现方法
2020/11/02 Python
搭建pypi私有仓库实现过程详解
2020/11/25 Python
css3实现input输入框颜色渐变发光效果代码
2014/04/02 HTML / CSS
购买200个世界上最好的内衣品牌:Bare Necessities
2017/02/11 全球购物
prAna官网:瑜伽、旅行和冒险服装
2019/03/10 全球购物
荷兰DOD药房中文官网:DeOnlineDrogist
2020/12/27 全球购物
市场部规章制度
2014/01/24 职场文书
咖啡馆创业计划书
2014/01/26 职场文书
基层党员四风问题自我剖析材料
2014/09/29 职场文书
民事调解书范文
2015/05/20 职场文书
李强感恩观后感
2015/06/17 职场文书
2015年小学师德师风建设工作总结
2015/10/23 职场文书
Html5通过数据流方式播放视频的实现
2021/04/27 HTML / CSS
解决Goland 同一个package中函数互相调用的问题
2021/05/06 Golang
SpringBoot集成Druid连接池连接MySQL8.0.11
2021/07/02 Java/Android