我应该避免异步处理 Promise 拒绝吗?

IT技术 javascript node.js es6-promise
2021-01-26 17:32:26

我刚刚安装了 Node v7.2.0 并了解到以下代码:

var prm = Promise.reject(new Error('fail'));

导致此消息:;

(node:4786) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4786) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

我理解这背后的原因,因为许多程序员可能经历过Error最终被Promise. 但是后来我做了这个实验:

var prm = Promise.reject(new Error('fail'));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
},
0)

这导致:

(node:4860) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4860) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:4860) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
fail

我基于PromiseRejectionHandledWarning假设异步处理Promise拒绝是/可能是一件坏事。

但这是为什么呢?

2个回答

注意:有关 Node v15 中的更改,请参阅下面的 2020 更新

“我应该避免异步处理 Promise 拒绝吗?”

这些警告有一个重要的目的,但要了解它是如何工作的,请参见以下示例:

试试这个:

process.on('unhandledRejection', () => {});
process.on('rejectionHandled', () => {});

var prm = Promise.reject(new Error('fail'));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

或这个:

var prm = Promise.reject(new Error('fail'));
prm.catch(() => {});

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

或这个:

var caught = require('caught');
var prm = caught(Promise.reject(new Error('fail')));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

免责声明:我是被捕获module的作者(是的,我是为这个答案写的)。

基本原理

它被添加到 Node作为v6 和 v7 之间重大更改之一问题 #830: Default Unhandled Rejection Detection Behavior 中对它进行了激烈的讨论,但对于异步附加拒绝处理程序的Promise应该如何表现没有普遍的共识 - 没有警告地工作,有警告地工作或通过终止程序被禁止使用. unhandled-rejections-spec项目的几个问题中进行了更多的讨论

此警告是为了帮助您找到忘记处理拒绝的情况,但有时您可能想避免它。例如,您可能希望发出一堆请求并将结果 promise 存储在一个数组中,以便稍后在程序的其他部分进行处理。

Promise相对于回调的优势之一是,您可以将创建Promise的位置与附加处理程序的位置(或多个位置)分开。这些警告使操作变得更加困难,但是您可以处理事件(我的第一个示例)或在创建不想立即处理的Promise的任何位置附加一个虚拟捕获处理程序(第二个示例)。或者你可以让一个module为你做这件事(第三个例子)。

避免警告

如果您分两步执行,附加空处理程序不会以任何方式改变存储的Promise的工作方式:

var prm1 = Promise.reject(new Error('fail'));
prm1.catch(() => {});

但是,这将不一样:

var prm2 = Promise.reject(new Error('fail')).catch(() => {});

prm2将是一个不同的Promiseprm1虽然prm1会因“失败”错误被拒绝,但prm2将得到解决,undefined这可能不是您想要的。

但是您可以编写一个简单的函数来使其像上面的两步示例一样工作,就像我对caughtmodule所做的那样

var prm3 = caught(Promise.reject(new Error('fail')));

这里prm3prm1.

见:https : //www.npmjs.com/package/caught

2017年更新

另请参见 Pull Request #6375: lib,src: "throw" on unhandled promise denied (截至 2017 年二月尚未合并),标记为Milestone 8.0.0

使 Promises“抛出”拒绝,这些拒绝像常规未捕获的错误一样退出【强调】

这意味着我们可以期望 Node 8.x 将有关此问题的警告更改为崩溃并终止进程错误,我们在今天编写程序时应该考虑到这一点,以避免将来出现意外。

另请参阅Node.js 8.0.0 跟踪问题 #10117

2020 更新

另请参阅 Pull Request #33021:process: Change default --unhandled-rejections=throw(已经合并并作为 v15 版本的一部分发布 - 请参阅:发行说明)再次使其成为例外:

从 Node.js 15 开始, 的默认模式unhandledRejection更改为throw(from warn)。throw模式下,如果unhandledRejection未设置挂钩,则将unhandledRejection作为未捕获的异常引发。拥有unhandledRejection钩子的用户应该不会看到行为的变化,并且仍然可以使用--unhandled-rejections=mode进程标志来切换模式

这意味着 Node 15.x 终于将这个问题即将发生的警告更改为错误,所以正如我在上面 2017 年所说的,我们在编写程序时绝对应该考虑到它,因为如果我们不这样做,它肯定会导致将运行时升级到 Node 15.x 或更高版本时出现问题。

为了确保完全准确,我认为澄清即将到来的确切更改很重要(不早于 Node 10,因为 9 已经没有发布)。如果 Promise 被垃圾回收,它只会以非零代码退出。所以只有当你没有对 Promise 的引用时,它才不可能被捕获。异步捕获的 Promise 永远不会导致进程退出,因为这会破坏许多标准模式。这些警告仍然会出现,因为没有办法知道你是否会抓住它。
2021-03-18 17:32:26
@JamesBillingham 是您在评论中澄清的内容,几天前发布的 Node v15 当前行为仍然如此?(答案更新中的更多信息)
2021-03-20 17:32:26
我认为节点 8 还没有崩溃,也许是节点 9。 process.on('unhandledRejection', () => {}); 和 process.on('rejectionHandled', () => {}); 真的应该多提倡。谢谢你的详细解释!
2021-04-13 17:32:26

我认为异步处理 Promise 拒绝是一件坏事。

是的,确实如此。

无论如何,您希望立即处理任何拒绝。如果您没有做到(并且可能永远无法处理它),您将收到警告。
我几乎没有遇到过您不想在被拒绝后立即失败的情况。即使你需要在失败后进一步等待,你也应该明确地这样做。

我从未见过无法立即安装错误处理程序的情况(尝试以其他方式说服我)。在您的情况下,如果您想要稍微延迟的错误消息,请执行

var prm = Promise.reject(new Error('fail'));

prm.catch((err) => {
    setTimeout(() => {
        console.log(err.message);
    }, 0);
});
我有一个问题,我得到的UnhandledPromiseRejectionWarning,即使我其实里面Webstorm处理被拒绝的Promise内部Generator当直接从控制台运行时,我没有收到警告。因此,即使您可能完全正确,您的回答也没有帮助我-尽管您刚刚回答了我的问题,但这不是您的错。尽管有更多层次,但它故意保持简单。
2021-03-21 17:32:26
你真是太好了。我现在准备了一个新问题,其中包含产生问题的整个代码。准备“代码墙”。
2021-04-04 17:32:26
如果您可以在新问题中发布您的实际代码,我可以帮助您:-)
2021-04-12 17:32:26