详解express与koa中间件模式对比
发布时间 - 2026-01-11 02:41:05 点击率:次起因

最近在学习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的中间件模式就不那么直观了,借用网上的图表示:
也就是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也是回调的一种形式)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# express
# koa
# 对比
# 中间件
# nodejs中Express与Koa2对比分析
# 关于express与koa的使用对比详解
# 浅谈redux
# express 中间件实现对比解析
# node.js中koa和express的差异对比
# 回调
# 递归
# 官网
# 让我们
# 也很
# 下一
# 可以看到
# 文档
# 都以
# 的是
# 看着
# 在这里
# 很好
# 会有
# 成了
# 好了
# 也就
# 也没
# 就不
# 还会
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
大连 网站制作,大连天途有线官网?
Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】
Laravel如何处理文件下载请求?(Response示例)
如何用景安虚拟主机手机版绑定域名建站?
Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】
如何安全更换建站之星模板并保留数据?
Laravel如何实现多表关联模型定义_Laravel多对多关系及中间表数据存取【方法】
Laravel如何使用Service Container和依赖注入?(代码示例)
如何挑选高效建站主机与优质域名?
高端云建站费用究竟需要多少预算?
如何用PHP快速搭建高效网站?分步指南
如何登录建站主机?访问步骤全解析
Laravel怎么生成二维码图片_Laravel集成Simple-QrCode扩展包与参数设置【实战】
Laravel如何实现用户密码重置功能?(完整流程代码)
电视网站制作tvbox接口,云海电视怎样自定义添加电视源?
如何在IIS中新建站点并解决端口绑定冲突?
bootstrap日历插件datetimepicker使用方法
如何在建站宝盒中设置产品搜索功能?
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
如何快速生成可下载的建站源码工具?
javascript中对象的定义、使用以及对象和原型链操作小结
教你用AI将一段旋律扩展成一首完整的曲子
mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?
微信小程序 五星评分(包括半颗星评分)实例代码
Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控
Laravel Artisan命令怎么自定义_创建自己的Laravel命令行工具完全指南
Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】
惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?
Claude怎样写结构化提示词_Claude结构化提示词写法【教程】
Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制
详解免费开源的.NET多类型文件解压缩组件SharpZipLib(.NET组件介绍之七)
Laravel如何使用Scope本地作用域_Laravel模型常用查询逻辑封装技巧【手册】
Laravel如何设置自定义的日志文件名_Laravel根据日期或用户ID生成动态日志【技巧】
zabbix利用python脚本发送报警邮件的方法
详解Android中Activity的四大启动模式实验简述
Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】
php嵌入式断网后怎么恢复_php检测网络重连并恢复硬件控制【操作】
高防网站服务器:DDoS防御与BGP线路的AI智能防护方案
Laravel如何处理CORS跨域请求?(配置示例)
Laravel Session怎么存储_Laravel Session驱动配置详解
黑客如何通过漏洞一步步攻陷网站服务器?
JS碰撞运动实现方法详解
高配服务器限时抢购:企业级配置与回收服务一站式优惠方案
Javascript中的事件循环是如何工作的_如何利用Javascript事件循环优化异步代码?
如何在景安服务器上快速搭建个人网站?
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
Windows10怎样连接蓝牙设备_Windows10蓝牙连接步骤【教程】
5种Android数据存储方式汇总
Android使用GridView实现日历的简单功能
长沙做网站要多少钱,长沙国安网络怎么样?

