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

Should I refrain from handling Promise rejection asynchronously?

刚刚安装了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 拒绝 异步 is/might 是一件坏事。

但这是为什么呢?

注意:请参阅下面的 2020 年更新以了解 Node v15 中的更改

“我应该避免异步处理 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);

免责声明:我是 caught 模块的作者(是的,我为这个答案写了它)。

理由

这是 added to Node as one of the Breaking changes between v6 and v7. There was a heated discussion about it in Issue #830: Default Unhandled Rejection Detection Behavior with no universal agreement on how promises with rejection handlers attached asynchronously should behave - work without warnings, work with warnings or be forbidden to use at all by terminating the program. More discussion took place in several issues of the unhandled-rejections-spec 项目。

这个警告是为了帮助您发现忘记处理拒绝但有时您可能想避免的情况。例如,您可能想要发出一堆请求并将结果承诺存储在一个数组中,只是为了稍后在程序的其他部分处理它。

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

避免警告

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

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

不过这会不一样:

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

此处 prm2prm1 的承诺不同。虽然 prm1 将被拒绝并出现 'fail' 错误,但 prm2 将被 undefined 解决,这可能不是您想要的。

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

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

此处prm3prm1相同。

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

2017 年更新

另请参阅拉取请求 #6375:lib,src: "throw" on unhandled promise rejections (not merged yet as of Febryary 2017) that is marked as Milestone 8.0.0

Makes Promises "throw" rejections which exit like regular uncaught errors. [emphasis added]

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

另见 Node.js 8.0.0 Tracking Issue #10117

2020 年更新

另请参阅 Pull Request #33021:process: Change default --unhandled-rejections=throw (already merged and released as part of the v15 release - see: release notes) 再次使其成为例外:

As of Node.js 15, the default mode for unhandledRejection is changed to throw (from warn). In throw mode, if an unhandledRejection hook is not set, the unhandledRejection is raised as an uncaught exception. Users that have an unhandledRejection hook should see no change in behavior, and it’s still possible to switch modes using the --unhandled-rejections=mode process flag.

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

I assume that handling a Promise rejection asynchronously is a bad thing.

确实如此。

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

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

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

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