關於next主要從三點來進行說明:
-
next的作用是什么?
-
我們應該在何時使用next?
-
next的內部實現機制是什么?
1.Next的作用
我們在定義express中間件函數的時候都會將第三個參數定義為next,這個next就是我們今天的主角,next函數主要負責將控制權交給下一個中間件。如果當前中間件沒有終結請求,並且next沒有被調用,那么請求將被掛起,后邊定義的中間件將得不到被執行的機會。
類比到django中來說:就是我們定義了很多個中間件,比如A,B,C。
如果我們在A中實現了一些功能,這是和,我們在A的回調函數內應該這么寫
app.all('/', function(req, res, next) {
// 在這里完成了一些功能。
next();
});
如果沒有在B中沒有寫next,也就是沒有寫next()。而且沒有直接retrun。那么,請求將會被掛起。
2.何時使用Next
從上邊的描述我們已經知道,next函數主要是用來確保所有注冊的中間件被一個接一個的執行,那么我們就應該在所有的中間件中調用next函數,但有一個特例,如果我們定義的中間件終結了本次請求,那就不應該再調用next函數,否則就可能會出問題,我們來看段代碼
app.get('/a', function(req, res, next) {
res.send('sucess');
next();
});
// catch 404 and forward to error handler
app.use(function(req, res, next) {
console.log(404);
var err = new Error('Not Found');
err.status = 404;
next(err);
});
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
發送請求"/a",控制台打印日志如下:
404
GET /a 500 6.837 ms - -
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:345:11)
為什么代碼會拋異常呢,就是因為我們在res.send之后調用了next函數,雖然我們本次的請求已經被終止,但后邊的404中間件依舊會被執行,而后邊的中間件試圖去向res的headers中添加屬性值,所以就會拋出上邊的異常。
讀到這你可能會有個疑問,如果我不在res.send后邊調用next函數,那后邊定義的404中間件是不是永遠都不會被執行到。現在我們刪除res.send后邊next函數調用,發送請求"/xxx",我們就會發現404中間件被執行了,(ㄒoㄒ),這不是和我們之前說的矛盾了嗎,我們的自定義中間件沒有調用next,但后邊定義的中間件仍舊被執行了,這究竟是為什么呢。看來只能求助源碼了~~~
3.Next的內部機制
function next(err) {
... //此處源碼省略
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
... //此處源碼省略
}
... //此處源碼省略
// this should be done for the layer
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
上邊就是express中next的源碼,為了更容易說明問題,對代碼進行了刪減。從上邊的源碼可以發現,next函數內部有個while循環,每次循環都會從stack中拿出一個layer,這個layer中包含了路由和中間件信息,然后就會用layer和請求的path就行匹配,如果匹配成功就會執行layer.handle_request,調用中間件函數。但如果匹配失敗,就會循環下一個layer(即中間件)。
現在我們就能解釋上邊提出的問題了,為什么我們的自定義中間件中沒調用next函數,但后邊的404中間件仍舊會被執行到,因為我們請求的"/xxx"匹配不到我們注冊的"/a"路由中間件,所以while循環會繼續往下執行,匹配404中間件成功,所以會執行404中間件。
注意:
app.use注冊的中間件,如果path參數為空,則默認為"/",而path為"/"的中間件默認匹配所有的請求