关于升级 node 16 引起的非兼容问题
在把项目从 node 14 升级到 node 16 的过程中,发现针对未经捕获的 Promise rejection, node 的处理行为发生变化:
在 node 提供的配置 option 中,有一项是 --unhandled-rejections=mode
, 在 node v15.0.0 之后,默认 mode
由 warn
变成了 throw
。
官网说明如下:
--unhandled-rejections=mode
用于控制当 unhandled rejection
发生时,node 的处理模式。提供了如下几种模式:
- throw: 直接触发
unhandleRejection
的 hook, 如果没写这个 hook, 那么直接将 rejection 作为异常抛出。 - strict: 将 rejection 作为异常抛出, 如果这个异常被处理, 则触发
unhandleRejection
的 hook - warn: 无论是否设置 unhandledRejection hook, 都触发 warning; 但不会打印废弃的 warning
- warn-with-error-code: 触发unhandledRejection hook,如果没有设置该 hook, 则触发 warning, 并将 process exit node 设置为 1
读完官网后,还有一些不明白的地方,所以以下是我经过思考后的理解:
什么是 “unhandled rejections”?
先看一个小例子:
function doSomethingAsync(){
return new Promise(function (resolve, reject){
setTimeout(function (){
resolve("success"); // Fulfill the promise successfully
}, 100);
});
}
doSomethingAsync().then(function (fulfilledResult){
console.log(fulfilledResult); // Prints "success"
});
doSomethingAsync
函数返回了一个Promise, 执行完毕后该 promise 状态变为 “fulfilled”, then
函数被调用。
但如果不这么顺利,在 promise 中执行发生错误会如何?
function doSomethingAsync(){
return new Promise(function (resolve, reject){
setTimeout(function (){
reject(new Error("something went terribly wrong!"));
}, 100);
});
}
doSomethingAsync()
.then(function (fulfilledResult){
// Never executes because the promise is rejected
console.log(fulfilledResult);
})
.catch(function (err){
console.log(err); // Prints "Error: something went terribly wrong"
});
可以看到错误被 catch
函数捕获。
由于每个 promise 都有潜在出错的可能,所以必须为每个 promise 设置 “then” 和 “catch” 两个分支。
那么,如果不 “catch” a promise rejection, 就是一个 “unhandled rejections”。
如何捕获 “unhandled rejections” ?
如果对 unhandled rejections
不予捕获,很可能导致程序直接崩溃,同时也不利于排查问题。
官网中提到 unhandleRejection
hook, 是用来捕获 “unhandled rejections”。
``unhandleRejection` 的官网解释是:
The ‘unhandledRejection’ event is emitted whenever a Promise is rejected and no error handler is attached to the promise within a turn of the event loop.
When programming with Promises, exceptions are encapsulated as “rejected promises”.
Rejections can be caught and handled using promise.catch() and are propagated through a Promise chain.
The ‘unhandledRejection’ event is useful for detecting and keeping track of promises that were rejected whose rejections have not yet been handled.
使用方式是:
import process from 'node:process';
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
// Application specific logging, throwing an error, or other logic here
});
与直觉不符合之处
在同步代码中,只在函数最上层添加 “try…catch…” 即可保证当前函数内部的所有错误都会被捕获。
但在 promise 链式调用过程中,即使在最上层的链中 catch(), 但任意链中的 promsie 没有 catch(), 那么也会出现 ‘unhandled rejections’ 的情况。
可能由于这一不符直觉的设计,导致开发代码时,容易出错。
得到的一些经验
在 node 项目中,最好加上 “unhandledRejection” hook, 避免进程突然崩溃,而不知问题原因。
在写 promise 时,尽量不要省略
catch()