关于升级 node 16 引起的非兼容问题

在把项目从 node 14 升级到 node 16 的过程中,发现针对未经捕获的 Promise rejection, node 的处理行为发生变化:

在 node 提供的配置 option 中,有一项是 --unhandled-rejections=mode, 在 node v15.0.0 之后,默认 modewarn 变成了 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()

Reference


323 Words

2023-02-04 15:42 +0000