node.js?express和koa中間件機(jī)制和錯(cuò)誤處理機(jī)制
一、前言
大家可能都知道koa是express核心原班人馬寫的,那么他們?yōu)槭裁匆趀xpress后再造一個(gè)koa的輪子呢? 今天就給大家?guī)?lái)一些分析。希望能夠起到一個(gè)拋磚引玉的作用。
其實(shí),這個(gè)題目也可以這么問, express有什么缺點(diǎn)? koa解決了一些express的什么問題? 這也在一些面試題中會(huì)這么問。所以,為了實(shí)現(xiàn)自己的理想(money), 志同道合的同志們可以隨我分析一下了。
我想先從express的一個(gè)非常重要的特征開始說(shuō)起,那就是 中間件。 中間件貫穿了express的始終,我們?cè)趀xpress中比較常用到應(yīng)用級(jí)的中間件,比如:
const app = require('express')();
app.use((req, res, next) => {
// 做一些事情。。。
next();
})再比如我們更常用到的路由級(jí)中間件。 我為什么要叫它是路由級(jí)的呢? 因?yàn)樗膬?nèi)部也同樣維護(hù)著一個(gè)next
app.get('/', (req, res, next) => {
res.send('something content');
})這里中間件我不詳細(xì)展開。 后面有我對(duì)中間件的詳細(xì)解析,歡迎大家圍觀。
那么我們可以看到,其中會(huì)有個(gè)關(guān)鍵的next, 它在express內(nèi)部做的是從棧中獲取下一個(gè)中間件的關(guān)鍵。
那么重點(diǎn)來(lái)了, 我們開始研究express這里的實(shí)現(xiàn)會(huì)隱藏什么問題。
二、中間件問題解析
通過(guò)一個(gè)例子來(lái)看:
const Express = require('express');
const app = new Express();
const sleep = () => new Promise(resolve => setTimeout(function(){resolve(1)}, 2000));
const port = 8210;
function f1(req, res, next) {
console.log('this is function f1....');
next();
console.log('f1 fn executed done');
}
function f2(req, res, next) {
console.log('this is function f2....');
next();
console.log('f2 fn executed done');
}
async function f3(req, res) {
console.log('f3 send to client');
res.send('Send To Client Done');
}
app.use(f1);
app.use(f2);
app.use(f3);
app.get('/', f3)
app.listen(port, () => console.log(`Example app listening on port ${port}!`))理想下的返回,和真正的返回,目前是沒有問題的。
this is function f1....
this is function f2....
f3 send to client
f1 fn executed done
f2 fn executed done好的,那么再繼續(xù)下一個(gè)例子。 在下一個(gè)例子中,其它都是沒有變化的,只有一個(gè)地方:
const sleep = () => new Promise(resolve => setTimeout(function(){resolve()}, 1000))
async function f3(req, res) {
await sleep();
console.log('f3 send to client');
res.send('Send To Client Done');
}這時(shí)你認(rèn)為的返回值順序是什么樣的呢?
可能會(huì)認(rèn)為跟上面的沒有變化,因?yàn)槲覀冊(cè)黾觓wait了,照道理應(yīng)該等待await執(zhí)行完了,再去執(zhí)行下面的代碼。 其實(shí)結(jié)果并不是。
返回的結(jié)果是:
this is function f1....
this is function f2....
f1 fn executed done
f2 fn executed done
f3 send to client
發(fā)生了什么?? 大家可能有點(diǎn)吃驚。但是,如果深入到express的源碼中去一探究竟,問題原因也就顯而易見了。
具體源碼我在這一篇中就不詳細(xì)分析了,直接說(shuō)出結(jié)論:
因?yàn)閑xpress中的中間件調(diào)用不是Promise 所以就算我們加了async await 也不管用。
那么koa中是怎么使用的呢?
const Koa = require('koa');
const app = new Koa();
const sleep = () => new Promise(resolve => setTimeout(function(){resolve()}, 1000))
app.use(async (ctx, next) => {
console.log('middleware 1 start');
await next();
console.log('middleware 1 end');
});
app.use(async (ctx, next) => {
await sleep();
console.log('middleware 2 start');
await next();
console.log('middleware 2 end');
});
app.use(async (ctx, next) => {
console.log('middleware 3 start')
ctx.body = 'test middleware executed';
})不出所料, 實(shí)現(xiàn)的順序是:
middleware 1 start
middleware 2 start
middleware 3 start
middleware 2 end
middleware 1 end
原因是: koa 內(nèi)部使用了Promise,所以能夠控制順序的執(zhí)行。
綜合上面的例子,我們知道了express中中間件使用的時(shí)候,如果不清楚原理,是容易踩坑的。 而koa通過(guò)使用async 和 await next() 實(shí)現(xiàn)洋蔥模型,即:通過(guò)next,到下一個(gè)中間件,只要下面的中間件執(zhí)行完成后,才一層層的再執(zhí)行上面的中間件,直到全部完成。
三、錯(cuò)誤邏輯捕獲
3.1 express的錯(cuò)誤捕獲邏輯
同樣,先看express在錯(cuò)誤邏輯的捕獲上有什么特點(diǎn):
app.use((req, res, next) => {
// c 沒有定義
const a = c;
});
// 錯(cuò)誤處理中間件
app.use((err, req, res, next) => {
if(error) {
console.log(err.message);
}
next()
})
process.on("uncaughtException", (err) => {
console.log("uncaughtException message is::", err);
})再看一個(gè)異步的處理:
app.use((req, res, next) => {
// c 沒有定義
try {
setTimeout(() => {
const a = c;
next()
}, 0)
} catch(e) {
console.log('異步錯(cuò)誤,能catch到么??')
}
});
app.use((err, req, res, next) => {
if(error) {
console.log('這里會(huì)執(zhí)行么??', err.message);
}
next()
})
process.on("uncaughtException", (err) => {
console.log("uncaughtException message is::", err);
})可以先猜一下同步和異步的會(huì)不會(huì)有所區(qū)別?
答案是: 有很大的區(qū)別!!
具體分開來(lái)看:
- 同步的時(shí)候, 不會(huì)觸發(fā)
uncaughtException, 而進(jìn)入了錯(cuò)誤處理的中間件。 - 異步的時(shí)候,
不會(huì)觸發(fā)錯(cuò)誤處理中間件,而會(huì)觸發(fā)uncaughtException
這中間發(fā)生了什么?
3.2 同步邏輯錯(cuò)誤獲取的底層邏輯
邏輯是: express內(nèi)部對(duì)同步發(fā)生的錯(cuò)誤進(jìn)行了攔截,所以,不會(huì)傳到負(fù)責(zé)兜底的node事件 uncaughtException ,如果發(fā)生了錯(cuò)誤,則直接繞過(guò)其它中間件,進(jìn)入錯(cuò)誤處理中間件。 那么,這里會(huì)有一個(gè)很容易被忽略的點(diǎn), 那就是,即使沒有錯(cuò)誤處理中間件做兜底,也不會(huì)進(jìn)入node的 uncaughtException, 這時(shí), 會(huì)直接報(bào) 500錯(cuò)誤。
3.3 異步邏輯錯(cuò)誤獲取的底層邏輯
還是因?yàn)閑xpress的實(shí)現(xiàn)并沒有把Promise考慮進(jìn)去, 它的中間件執(zhí)行是同步順序執(zhí)行的。 所以如果有異步的,那么錯(cuò)誤處理中間件實(shí)際是兜不住的,所以,express對(duì)這種中間件中的異步處理錯(cuò)誤無(wú)能為力。
從上面的異步觸發(fā)例子來(lái)看, 除了錯(cuò)誤處理中間件沒有觸發(fā),我們當(dāng)中的try catch也沒有觸發(fā)。這是一個(gè)大家可能都會(huì)踩到的坑。 這里其實(shí)是與javascript的運(yùn)行機(jī)制相關(guān)了。具體原因見本篇 JavaScript異步隊(duì)列進(jìn)行try catch時(shí)的問題解決
所以要想去catch 當(dāng)前的錯(cuò)誤,那么就需要用 async await
app.use(async (req, res, next) => {
try {
await (() => new Promise((resolve, reject) => {
http.get('http://www.example.com/testapi/123', res => {
reject('假設(shè)錯(cuò)誤了');
}).on('error', (e) => {
throw new Error(e);
})
}))();
} catch(e) {
console.log('異步錯(cuò)誤,能catch到么??')
}
});這樣,我們的catch不僅可以獲取到, uncaughtException也可以獲取到。
3.4 koa的錯(cuò)誤獲取邏輯
總體上是跟express差不多,因?yàn)閖s的底層處理還是一致的。但還是使用上有所差異。
上面也提過(guò)洋蔥模型,特點(diǎn)是最開始的中間件,在最后才執(zhí)行完畢,所以,在koa上,可以把錯(cuò)誤處理中間件放到中間件邏輯最前面。
const http = require('http');
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next)=>{
try {
await next();
} catch (error) {
// 響應(yīng)用戶
ctx.status = 500;
ctx.body = '進(jìn)入默認(rèn)錯(cuò)誤中間件';
// ctx.app.emit('error', error); // 觸發(fā)應(yīng)用層級(jí)錯(cuò)誤事件
}
});
app.use(async (ctx, next) => {
await (() => new Promise((resolve, reject) => {
http.get('http://www.example.com/testapi/123', res => {
reject('假設(shè)錯(cuò)誤了');
}).on('error', (e) => {
throw new Error(e);
})
}))();
await next();
})上面的代碼, reject出的錯(cuò)誤信息,會(huì)被最上面的錯(cuò)誤處理中間件捕獲。總結(jié)來(lái)說(shuō),js的底層機(jī)制是一樣的, 只是使用方法和細(xì)節(jié)點(diǎn)上不一樣,大家在用的時(shí)候注意一下,
到此這篇關(guān)于node.js express和koa中間件機(jī)制和錯(cuò)誤處理機(jī)制的文章就介紹到這了,更多相關(guān)node.js express和koa內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
yarn?install命令報(bào)錯(cuò)warning?package-lock.json?found解決辦法
這篇文章主要給大家介紹了關(guān)于yarn?install命令報(bào)錯(cuò)warning?package-lock.json?found的解決辦法,文中通過(guò)圖文將解決的辦法介紹的非常詳細(xì),還分享了更多yarn install遇到的報(bào)錯(cuò)及解決方案,需要的朋友可以參考下2024-02-02
node.js的http.createServer過(guò)程深入解析
這篇文章主要給大家介紹了關(guān)于node.js的http.createServer過(guò)程的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用node.js具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
nodejs?express路由匹配控制及Router模塊化使用詳解
這篇文章主要為大家介紹了nodejs?express路由匹配控制及Router模塊化使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
jwt在node中的應(yīng)用實(shí)踐(安裝配置封裝)
這篇文章主要為大家介紹了jwt在node中的應(yīng)用實(shí)踐包括安裝配置封裝,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
用npm install時(shí)報(bào)錯(cuò)node-sass npm ERR command
在用npm install時(shí)報(bào)錯(cuò)npm ERR! path D:…\node-sass和npm ERR! command failed 問題,本文給大家介紹了如何解決這個(gè)問題,文中通過(guò)圖文給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03
Nodejs 獲取時(shí)間加手機(jī)標(biāo)識(shí)的32位標(biāo)識(shí)實(shí)現(xiàn)代碼
本文給大家分享nodejs獲取時(shí)間加手機(jī)標(biāo)識(shí)的32位標(biāo)識(shí)實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-03-03

