详解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 String 的扩展方法集合
Jun 01 Javascript
js 判断上传文件大小及格式代码
Nov 13 Javascript
jQuery中;function($,undefined) 前面的分号的用处
Dec 17 Javascript
javascript与jquery中的this关键字用法实例分析
Dec 24 Javascript
jQuery中delegate()方法的用法详解
Oct 13 Javascript
最全的JavaScript开发工具列表 总有一款适合你
Jun 29 Javascript
完美解决axios跨域请求出错的问题
Feb 05 Javascript
vue v-model实现自定义样式多选与单选功能
Jul 05 Javascript
vue实现新闻展示页的步骤详解
Apr 11 Javascript
Node.js 获取微信JS-SDK CONFIG的方法示例
May 21 Javascript
Json实现传值到后台代码实例
Jun 30 Javascript
vue-model实现简易计算器
Aug 17 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五种设计模式小结
2011/03/23 PHP
IIS7.X配置PHP运行环境小结
2011/06/09 PHP
Laravel配置全局公共函数的方法步骤
2019/05/09 PHP
js 判断浏览器类型 去全角、半角空格 自动关闭当前窗口
2009/04/10 Javascript
js获取height和width的方法说明
2013/01/06 Javascript
让复选框只能选择一项的方法
2013/10/08 Javascript
apply和call方法定义及apply和call方法的区别
2015/11/15 Javascript
微信小程序 loading(加载中提示框)实例
2016/10/28 Javascript
简单实现JS倒计时效果
2016/12/23 Javascript
Javascript的this用法
2017/01/16 Javascript
详谈表单格式化插件jquery.serializeJSON
2017/06/23 jQuery
js实现随机点名系统(实例讲解)
2017/10/18 Javascript
AngularJS $http post 传递参数数据的方法
2018/10/09 Javascript
vue父组件触发事件改变子组件的值的方法实例详解
2019/05/07 Javascript
微信小程序云开发之使用云数据库
2019/05/17 Javascript
详解wepy开发小程序踩过的坑(小结)
2019/05/22 Javascript
Vue+ElementUI table实现表格分页
2019/12/14 Javascript
python实现向ppt文件里插入新幻灯片页面的方法
2015/04/28 Python
Python实现的计数排序算法示例
2017/11/29 Python
Python实现k-means算法
2018/02/23 Python
Django数据库连接丢失问题的解决方法
2018/12/29 Python
Python read函数按字节(字符)读取文件的实现
2019/07/03 Python
windows安装TensorFlow和Keras遇到的问题及其解决方法
2019/07/10 Python
Numpy对数组的操作:创建、变形(升降维等)、计算、取值、复制、分割、合并
2019/08/28 Python
Python xpath表达式如何实现数据处理
2020/06/13 Python
区分python中的进程与线程
2020/08/13 Python
HTML5实现分享到微信好友朋友圈QQ好友QQ空间微博二维码功能
2018/01/03 HTML / CSS
肯尼亚网上商城:Kilimall
2016/08/20 全球购物
管理科学大学生求职信
2013/11/13 职场文书
家长给老师的道歉信
2014/01/13 职场文书
周年庆促销方案
2014/03/15 职场文书
南湾猴岛导游词
2015/02/09 职场文书
讲座开场白台词和结束语
2015/05/29 职场文书
传单、海报早OUT了,另类传单营销方案送给你!
2019/07/15 职场文书
《初涉尘世》读后感3篇
2020/01/10 职场文书
CKAD认证中部署k8s并配置Calico插件
2022/03/31 Servers