Это может стать понятным, если мы подумаем о throw
и catch
, работающих на стеке :
throw
: идет вниз по стеку, пока не найдет обработчик, затемпродолжается оттуда.
catch
добавляет обработчик ошибок в стек.
Для синхронного кода это можно представить как:
// Legend:
-> function call
<- function returns
http.request -> express.handler -> [try] -> your function -> nested call -> Throw!
<- <- [catch] <-----------------------------------
Теперь, когда вы запускаетеасинхронное действие, обратный вызов когда-нибудь будет вызван, и этот обратный вызов закончится в новом стеке:
// the current stack:
http.request -> express.handler -> [try] -> your function -> start async action
<- <- <- <- <-
// somewhen later, the timeout calls back
timer -> your setTimeout callback -> nested call -> Throw!
Crash! <-----------------------------------------------------
Теперь, когда Express присоединяет обработчик catch
к обратному вызову в ваш код:
Express.get = function(callback) {
//...
try {
callback(req, res, next);
} catch(error) {
// handle error
}
};
, который будет обрабатывать ошибки, но только синхронные (это упрощенно, фактический код здесь )
Теперь, что нужно сделать, чтобы справиться с этим?
В основном: Оберните каждый обратный вызов в обещание (поскольку это облегчает асинхронную обработку ошибок):
const delay = ms => new Promise(res => setTimeout(res, ms));
затем await
каждое созданное вами обещание и оберните все это в try
/ catch
:
app.get(async (req, res) => {
try {
await delay(2000);
const p = q + d;
} catch(error) {
res.status(500).send("whoops");
}
});
Это работает, потому что await
"держит стек"(но только одна из async
функций, которые await
при вложенных вызовах, поэтому нам нужно добавить наш собственный try
/ catch
, поскольку Express не await
при вызове обратного вызова), поэтому ошибка внутриодна из вложенных await
ed функций обратится к нашему обработчику ошибок.
http.request -> express.handler -> [async] -> [try] -> nested call -> [await]
<- <- <-
// synchronous code returned, the [async] stack will be kept
[async] -> [try] -> nested call -> [await]
// somewhen, the promise resolves, execution coninues:
[async] -> [try] -> nested call -> Throw!
<- <- [catch] <--------------