Express和Koa都是基于Nodejs平台的web框架,也是目前比较常见的用于快速开发web服务的框架,且两者都是基于middleware的方式去处理客户端请求,那么两者有何区别呢?
简单点说就是,“Express是直线型,Koa是洋葱模型”。(喊口号!!!)
我们先来看看下面的示例代码:
// for express example
const express = require('express');
const app = express();
function cb1(req, res, next) {
console.log('>>>>>>cb1');
next();
console.log('<<<<<>>cb2<<<');
res.send('hello world');
}
app.use('/', [cb1, cb2]);
app.listen(3000);
// for koa2 example
const koa = require('koa2');
const app = koa();
function cb1(ctx, next) {
console.log('>>>>>>cb1');
next();
console.log('<<<<<>>cb2<<<');
ctx.body = 'hello world';
}
app.use(cb1);
app.use(cb2);
app.listen(3000);
以上两段代码的输出皆为:
>>>>>>cb1
>>>cb2<<<
<<<<<
所以,当middleware为同步函数时,两者从执行结果上来看并无区别。
我们再来看看下面的示例代码:
// for express example
const express = require('express');
const app = express();
async function cb1(req, res, next) {
console.log('>>>>>>cb1');
await next();
console.log('<<<<< {
setTimeout(resolve, 500);
}).then(() => {
console.log('>>>cb2<<<');
res.send('hello world');
});
}
app.use('/', [cb1, cb2]);
app.listen(3000);
// for koa2 example
const koa = require('koa2');
const app = new koa();
async function cb1(ctx, next) {
console.log('>>>>>>cb1');
await next();
console.log('<<<<< {
setTimeout(resolve, 500);
}).then(() => {
console.log('>>>cb2<<<');
ctx.body = 'hello world';
});
}
app.use(cb1);
app.use(cb2);
app.listen(3000);
express-example的输出为:
>>>>>>cb1
>>>>>>cb1
>>>cb2>>>
而koa2-example的输出为:
>>>>>>cb1
>>>cb2<<<
<<<<<
从上面的例子可以看出,当middleware为异步函数时,Express和Koa的执行流程是不同的。Express的返回结果并不是我们设想中的结果,是什么原因导致的行为差异呢?下面,让我们一起来简单的分析下Express和Koa中执行middleware部分的源码片段。
在Express中,执行middleware的逻辑代码主要位于lib/router/route.js和lib/router.layer.js文件:
// route.js
Route.prototype.dispatch = function dispatch(req, res, done) {
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = this;
next();
function next(err) {
// signal to exit route
if (err && err === 'route') {
return done();
}
// signal to exit router
if (err && err === 'router') {
return done(err)
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next(err);
}
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
//layer.js
Layer.prototype.handle_error = function handle_error(error, req, res, next) {
var fn = this.handle;
if (fn.length !== 4) {
// not a standard error handler
return next(error);
}
try {
fn(error, req, res, next);
} catch (err) {
next(err);
}
};
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
在Koa2中,执行middleware的逻辑代码主要位于koa-compose/index.js文件:
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
由上可知,Express中middleware的next参数是一个普通的函数对象,而Koa中middleware的next参数是一个promise对象。所以当我们挂载异步的middleware时,Express并不能像Koa一样,在middleware中使用await去等待下一个middleware执行完成之后,再执行当前middleware的后续逻辑。这就是为什么“Express是直线型,Koa是洋葱模型”的根本原因。
以上就是我对于Express和Koa框架的理解,希望对你有帮助。如果上述内容有错误的地方,欢迎大家指正,谢谢~