Node.js中try/catch无法捕获Promise.then内部抛出的异常导致进程崩溃的问题咨询
哥们,这个问题我之前也碰到过,其实核心原因是同步try/catch管不到异步代码里的异常,咱们一步步拆解来看:
为什么会崩溃?
你写的外层try/catch只能捕获同步执行阶段的异常,比如if (isCodeExpired)里的throw,这是同步代码,执行时还在try块的上下文里,所以能被catch抓到。
但gtPostRequestWithXMLParsing(requestBody).then(...)是异步逻辑:当try块里的代码全部执行完(包括发起这个Promise请求),catch块就已经执行完毕了。等后续Promise成功触发then回调时,此时已经脱离了原来的try/catch上下文,你在then里抛出的异常就变成了未处理的Promise拒绝(Unhandled Promise Rejection)。在Node.js中,这种未处理的Promise拒绝默认会导致进程崩溃(尤其是v14及以上版本的Node.js,这个行为是默认开启的)。
怎么解决?
这里有两种常用的处理方式,选哪种看你的代码风格:
方法1:用async/await把异步逻辑“同步化”
把你的路由处理函数改成async函数,用await等待Promise完成,这样异步逻辑就会被纳入try/catch的管辖范围:
async confirmLoginWithCode(req, res, next) { try { if (isCodeExpired) { throw ApiError.BadResultField('SMS code expired'); } // 用await等待Promise结果,此时后续的throw会被try/catch捕获 const resultMessage = await gtPostRequestWithXMLParsing(requestBody); throw ApiError.BadResultField(resultMessage); } catch (e) { next(e); } };
await会暂停函数执行,直到Promise状态变更:如果Promise成功,就返回结果;如果Promise失败,会直接抛出异常被catch捕获。而你在await之后主动抛出的异常,也属于同步执行的逻辑,自然能被外层try/catch抓到。
方法2:在Promise链上添加catch回调
如果不想用async/await,那就在Promise链的末尾加上.catch(),专门处理异步阶段的异常:
confirmLoginWithCode(req, res, next) { try { if (isCodeExpired) { throw ApiError.BadResultField('SMS code expired'); } gtPostRequestWithXMLParsing(requestBody) .then((resultMessage) => { throw ApiError.BadResultField(resultMessage); }) // 这里捕获then里抛出的异常,调用next传递给错误中间件 .catch((e) => { next(e); }); } catch (e) { // 处理同步阶段的异常 next(e); } };
这种方式下,异步逻辑的异常会被Promise链的catch捕获,然后通过next(e)传递给Express的错误处理中间件,不会导致进程崩溃。
备注:内容来源于stack exchange,提问作者Andrej Denisov

