提到 Node.js 開發,不得不提目前煊赫一時的2大框架 Express 和 Koa。javascript
Express 是一個保持最小規模的靈活的 Node.js Web 應用程序開發框架,爲 Web 和移動應用程序提供一組強大的功能。目前使用人數衆多。java
Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成爲 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。 經過利用 async 函數,Koa 幫你丟棄回調函數,並有力地加強錯誤處理。 Koa 並無捆綁任何中間件, 而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程序。git
相信對這兩大框架有一些瞭解的人都或多或少的會了解其中間件機制,Express 爲線型模型,而 Koa 則爲洋蔥型模型。這個系列的博客主要講解 Express 和 Koa 的中間件機制,本篇將主要講解 Express 的中間件機制。github
connect 曾經是 express 3.x 以前的核心,而 express 4.x 已經把 connect 移除,在 express 中本身實現了 connect 的接口,因此咱們本篇中的源碼解釋將直接使用 connect 源碼。web
下面將使用 Express 實現一個簡單的 demo 來進行中間件機制的講解express
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()bash
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);
複製代碼
輸出結果爲app
第一個中間件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的源碼僅有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 中的中間件取出、執行。
這裏即可以解釋上文中異步和非異步過程當中所輸出的結果的差別了。
connect 的實現其基本原理是維護一個 stack 數組,將所須要掛載的中間件處理後所有 push 到數組內,以後在數組內循環執行 next 方法,直至全部中間件掛載完畢,固然這過程當中會作一些異常、兼容等的處理。
Express與Koa中間件機制分析(二) 主要分析 Koa2 的中間件實現機制