nodejs對(duì)express中next函數(shù)的一些理解
最近公司在使用node做前后端分離,采用的web框架是express,所以對(duì)express框架進(jìn)行了深入的了解,前段時(shí)間寫了篇關(guān)于express路由的文章,但是在那篇文章中貌似少了一個(gè)很重要的內(nèi)容,就是express的next,所以今天單獨(dú)來(lái)說(shuō)說(shuō)express的next。
關(guān)于next主要從三點(diǎn)來(lái)進(jìn)行說(shuō)明:
- next的作用是什么?
- 我們應(yīng)該在何時(shí)使用next?
- next的內(nèi)部實(shí)現(xiàn)機(jī)制是什么?
Next的作用
我們?cè)诙xexpress中間件函數(shù)的時(shí)候都會(huì)將第三個(gè)參數(shù)定義為next,這個(gè)next就是我們今天的主角,next函數(shù)主要負(fù)責(zé)將控制權(quán)交給下一個(gè)中間件,如果當(dāng)前中間件沒(méi)有終結(jié)請(qǐng)求,并且next沒(méi)有被調(diào)用,那么請(qǐng)求將被掛起,后邊定義的中間件將得不到被執(zhí)行的機(jī)會(huì)。
何時(shí)使用Next
從上邊的描述我們已經(jīng)知道,next函數(shù)主要是用來(lái)確保所有注冊(cè)的中間件被一個(gè)接一個(gè)的執(zhí)行,那么我們就應(yīng)該在所有的中間件中調(diào)用next函數(shù),但有一個(gè)特例,如果我們定義的中間件終結(jié)了本次請(qǐng)求,那就不應(yīng)該再調(diào)用next函數(shù),否則就可能會(huì)出問(wèn)題,我們來(lái)看段代碼
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: {} }); });
發(fā)送請(qǐng)求"/a",控制臺(tái)打印日志如下:
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)
為什么代碼會(huì)拋異常呢,就是因?yàn)槲覀冊(cè)趓es.send之后調(diào)用了next函數(shù),雖然我們本次的請(qǐng)求已經(jīng)被終止,但后邊的404中間件依舊會(huì)被執(zhí)行,而后邊的中間件試圖去向res的headers中添加屬性值,所以就會(huì)拋出上邊的異常。
讀到這你可能會(huì)有個(gè)疑問(wèn),如果我不在res.send后邊調(diào)用next函數(shù),那后邊定義的404中間件是不是永遠(yuǎn)都不會(huì)被執(zhí)行到。現(xiàn)在我們刪除res.send后邊next函數(shù)調(diào)用,發(fā)送請(qǐng)求"/xxx",我們就會(huì)發(fā)現(xiàn)404中間件被執(zhí)行了,(ㄒoㄒ),這不是和我們之前說(shuō)的矛盾了嗎,我們的自定義中間件沒(méi)有調(diào)用next,但后邊定義的中間件仍舊被執(zhí)行了,這究竟是為什么呢??磥?lái)只能求助源碼了~~~
Next的內(nèi)部機(jī)制
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的源碼,為了更容易說(shuō)明問(wèn)題,對(duì)代碼進(jìn)行了刪減。從上邊的源碼可以發(fā)現(xiàn),next函數(shù)內(nèi)部有個(gè)while循環(huán),每次循環(huán)都會(huì)從stack中拿出一個(gè)layer,這個(gè)layer中包含了路由和中間件信息,然后就會(huì)用layer和請(qǐng)求的path就行匹配,如果匹配成功就會(huì)執(zhí)行l(wèi)ayer.handle_request,調(diào)用中間件函數(shù)。但如果匹配失敗,就會(huì)循環(huán)下一個(gè)layer(即中間件)。
現(xiàn)在我們就能解釋上邊提出的問(wèn)題了,為什么我們的自定義中間件中沒(méi)調(diào)用next函數(shù),但后邊的404中間件仍舊會(huì)被執(zhí)行到,因?yàn)槲覀冋?qǐng)求的"/xxx"匹配不到我們注冊(cè)的"/a"路由中間件,所以while循環(huán)會(huì)繼續(xù)往下執(zhí)行,匹配404中間件成功,所以會(huì)執(zhí)行404中間件。
注意:app.use注冊(cè)的中間件,如果path參數(shù)為空,則默認(rèn)為"/",而path為"/"的中間件默認(rèn)匹配所有的請(qǐng)求。
有一點(diǎn)需要特別指出,其實(shí)我們?cè)诙x路由中間件的時(shí)候函數(shù)的第三個(gè)參數(shù)next和我們定義非路由中間件的函數(shù)的第三個(gè)參數(shù)next不是同一個(gè)next,我們?cè)谏线吙吹降氖欠锹酚芍虚g件的next,而路由中間件的next函數(shù)是這樣的
function next(err) { if (err && err === 'route') { return done(); } 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); } }
這個(gè)next比上邊的那個(gè)next要簡(jiǎn)單很多,它負(fù)責(zé)同一個(gè)路由的多個(gè)中間件的控制權(quán)的傳遞,并且它會(huì)接收一個(gè)參數(shù)"route",如果調(diào)用next(“route”),則會(huì)跳過(guò)當(dāng)前路由的其它中間件,直接將控制權(quán)交給下一個(gè)路由。
最后有必要再說(shuō)一說(shuō)next(err),next(err)是如何將控制權(quán)傳遞到錯(cuò)誤處理中間件的,從前邊的代碼我們知道,當(dāng)調(diào)用next(err)是,express內(nèi)部會(huì)調(diào)用layer.handle_error,那我們來(lái)看看它的源碼
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); } };
代碼中的fn就是中間件函數(shù),express會(huì)對(duì)fn的參數(shù)個(gè)數(shù)進(jìn)行判斷,如果參數(shù)個(gè)數(shù)不等于4則認(rèn)為不是錯(cuò)誤處理中間件,則繼續(xù)調(diào)用next(err),這樣就會(huì)進(jìn)入到下一個(gè)中間件函數(shù),繼續(xù)進(jìn)行參數(shù)個(gè)數(shù)判斷,如此方式一直到某個(gè)中間件函數(shù)的參數(shù)個(gè)數(shù)是4,就認(rèn)為找到了錯(cuò)誤處理中間件,然后執(zhí)行此中間件函數(shù)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
nodejs中sleep功能實(shí)現(xiàn)暫停幾秒的方法
本篇文章主要介紹了nodejs中sleep功能實(shí)現(xiàn)暫停幾秒的方法,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07npm報(bào)錯(cuò):request to httpsregistry.npm.taobao.org 
這篇文章主要介紹了npm報(bào)錯(cuò):request to httpsregistry.npm.taobao.org failed, reason certificate has expired的解決方案,文中有詳細(xì)的解決方案,需要的朋友可以參考下2024-03-03node express如何實(shí)現(xiàn)json轉(zhuǎn)Excel
這篇文章主要介紹了node express如何實(shí)現(xiàn)json轉(zhuǎn)Excel問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08在Node.js中將SVG圖像轉(zhuǎn)換為PNG,JPEG,TIFF,WEBP和HEIF格式的方法
這篇文章主要介紹了在Node.js中將SVG圖像轉(zhuǎn)換為PNG,JPEG,TIFF,WEBP和HEIF格式的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Node.js上傳文件功能之服務(wù)端如何獲取文件上傳進(jìn)度
這篇文章主要介紹如何利用progress-stream獲取文件上傳進(jìn)度,以及該組件使用過(guò)程中的注意事項(xiàng)2018-02-02Node.js設(shè)置定時(shí)任務(wù)之node-schedule模塊的使用詳解
node-schedule是 Node.js 的一個(gè)定時(shí)任務(wù)(crontab)模塊。這篇文章主要介紹了Node.js設(shè)置定時(shí)任務(wù)之node-schedule模塊的使用,需要的朋友可以參考下2020-04-04Express下采用bcryptjs進(jìn)行密碼加密的方法
本篇文章主要介紹了Express下采用bcryptjs進(jìn)行密碼加密的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Node實(shí)現(xiàn)搜索框進(jìn)行模糊查詢
這篇文章主要為大家詳細(xì)介紹了Node實(shí)現(xiàn)搜索框進(jìn)行模糊查詢,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06