提到 Node.js 开辟,不得不提现在炙手可热的2大框架 Express 和 Koa。
Express 是一个坚持最小范围的天真的 Node.js Web 运用程序开辟框架,为 Web 和挪动运用程序供应一组壮大的功用。现在运用人数浩瀚。
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 运用和 API 开辟领域中的一个更小、更富有表现力、更硬朗的基石。 经由历程应用 async 函数,Koa 帮你抛弃回调函数,并有力地加强错误处置惩罚。 Koa 并没有绑缚任何中间件, 而是供应了一套文雅的要领,协助您疾速而愉快地编写服务端运用程序。
置信对这两大框架有一些相识的人都或多或少的会相识个中间件机制,Express 为线型模子,而 Koa 则为洋葱型模子。这个系列的博客重要解说 Express 和 Koa 的中间件机制,本篇将重要解说 Express 的中间件机制。
Express 中间件
connect 曾经是 express 3.x 之前的中心,而 express 4.x 已把 connect 移除,在 express 中本身完成了 connect 的接口,所以我们本篇中的源码诠释将直接运用 connect 源码。
示例
下面将运用 Express 完成一个简朴的 demo 来举行中间件机制的解说
var express = require('express');
var app = express();
app.use(function (req, res, next) {
console.log('第一个中间件start');
setTimeout(() => {
next();
}, 1000)
console.log('第一个中间件end');
});
app.use(function (req, res, next) {
console.log('第二个中间件start');
setTimeout(() => {
next();
}, 1000)
console.log('第二个中间件end');
});
app.use('/foo', function (req, res, next) {
console.log('接口逻辑start');
next();
console.log('接口逻辑end');
});
app.listen(4000);
此时的输出比较相符我们对 Express 线性的明白,其输出为
第一个中间件start
第一个中间件end
第二个中间件start
第二个中间件end
接口逻辑start
接口逻辑end
然则,假如我们取消掉中间件内部的异步处置惩罚直接挪用 next()
var express = require('express');
var app = express();
app.use(function (req, res, next) {
console.log('第一个中间件start');
next()
console.log('第一个中间件end');
});
app.use(function (req, res, next) {
console.log('第二个中间件start');
next()
console.log('第二个中间件end');
});
app.use('/foo', function (req, res, next) {
console.log('接口逻辑start');
next();
console.log('接口逻辑end');
});
app.listen(4000);
输出效果为
第一个中间件start
第二个中间件start
接口逻辑start
接口逻辑end
第二个中间件end
第一个中间件end
这类效果不是和 Koa 的输出很相似吗?是的,然则它和剥洋葱模子照样不一样的,实在这类输出的效果是因为代码的同步运转致使的,并不是说 Express 不是线性的模子。
当我们的中间件内没有举行异步操纵时,实在我们的代码末了是以下面这类体式格局运转的
app.use(function middleware1(req, res, next) {
console.log('第一个中间件start')
// next()
(function (req, res, next) {
console.log('第二个中间件start')
// next()
(function (req, res, next) {
console.log('接口逻辑start')
// next()
(function handler(req, res, next) {
// do something
})()
console.log('接口逻辑end')
})()
console.log('第二个中间件end')
})()
console.log('第一个中间件end')
})
致使这类运转体式格局的实在就是 connect 的完成体式格局,接下来我们举行其源码的剖析
connect 源码剖析
connect的源码唯一200多行,然则这里解说只挑选个中部份中心代码,并不是完全代码,检察悉数代码请移步 github
中间件的挂载重要依靠 proto.use 和 proto.handle,这里我们删掉部份 if 推断以使我们更专注于其内部道理的完成
proto.use = function use(route, fn) {
var handle = fn;
var path = route;
// 这里是对直接填入回调函数的举行容错处置惩罚
// default route to '/'
if (typeof route !== 'string') {
handle = route;
path = '/';
}
.
.
.
this.stack.push({ route: path, handle: handle });
return this;
};
proto.use 重要将我们须要挂载的中间件存储在其本身 stack 属性上,同时举行部份兼容处置惩罚,这一块比较轻易明白。个中间件机制的中心为 proto.handle 内部 next 要领的完成。
proto.handle = function handle(req, res, out) {
var index = 0;
var stack = this.stack;
function next(err) {
// next callback
var layer = stack[index++];
// all done
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();
};
在删除到部份非中心代码后,能够清楚的看到,proto.handle 的中心就是 next 要领的完成和递归挪用,对存在于 stack 中的中间件掏出、实行。
这里便能够诠释上文中异步和非异步历程当中所输出的效果的差别了。
- 当有异步代码时,将会直接跳过继承实行,此时的 next 要领并未实行,须要守候当前行列中的事宜悉数实行终了,所以此时我们输出的数据是线性的。
- 当 next 要领直接实行时,本质上一切的代码都已为同步,所以层层嵌套,最外层的一定会在末了,输出了相似剥洋葱模子的效果。
总结
connect 的完成其基本道理是保护一个 stack 数组,将所须要挂载的中间件处置惩罚后悉数 push 到数组内,以后在数组内轮回实行 next 要领,直至一切中间件挂载终了,固然这历程当中会做一些非常、兼容等的处置惩罚。