Node.js 中的 UnhandledPromiseRejectionWarning 问题

问题引入:今天在 Gulp 构建任务中出现一个 html 解析错误,但是并没有报错,也没有中断 gulp 构建任务的执行,而是出现 UnhandledPromiseRejectionWarning 的警告,所以会误以为构建成功,这篇文章将对此进行探究并解决该问题。

1
2
3
4
5
(node:24866) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error in plugin 'gulp-posthtml'
Message:
Parse Error: <img id="titleIcon" class$="{{getStypeType_(info.stype)}}" src$="{{getTitleIcon_(in
...
(node:24866) [DEP0018] 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.

关于 Unhandled Rejection

一个 Promise 是一个异步操作的状态机,其可能处于这三种状态之一

  • pending:异步操作还在执行中
  • fulfilled:异步操作已经完成
  • rejected:异步操作执行失败

Node.js 6.6.0 added a sporadically useful bug/feature: logging unhandled promise rejections to the console by default.

在 Node.js 6.6.0 中增加了一个特性:对 Promise 中未处理的 rejection 默认会输出 UnhandledPromiseRejectionWarning 提示

例如:test.js 中有如下代码:

1
2
3
new Promise((resolve, reject) => {
setTimeout(() => reject('woops'), 500);
});

node test.js 执行:

1
2
(node:47122) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): error
(node:47122) [DEP0018] 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

另一种情况是直接在 Promise 中抛出异常:

1
new Promise(() => { throw new Error('exception!'); });

执行后也会有 UnhandledPromiseRejectionWarning 的警告:

1
2
(node:47657) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: exception!
(node:47657) [DEP0018] 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.

Promise API 中有 .catch() 这个方法,可以用来处理捕捉 rejection 进行处理

1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
setTimeout(() => reject('error'), 500);
})
.catch(error => console.log('caught', error))
new Promise(() => { throw new Error('exception!'); })
.catch(error => console.log('caught', error.message))

但是注意:

1
2
new Promise((_, reject) => reject(new Error('woops')))
.catch(error => { console.log('caught', err.message); });

这个例子中虽然用 .catch() 捕捉处理了 Promise 中的 rejection;但是注意在 err.message 中的 err 是未定义的,代码执行时会抛出错误,由于没有后续的处理,所以也会输出 UnhandledPromiseRejectionWarning 的警告

1
2
(node:47918) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): ReferenceError: err is not defined
(node:47918) [DEP0018] 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.

所以稍不注意就会引起 Promise 中的 unhandled rejections 😨

unhandledRejection 事件

在 node process 中有一个 unhandledRejection 事件,当没有对 Promise 的 rejection 进行处理就会抛出这个事件(这只对原生 Promise 有效)

The unhandledrejection event is fired when a JavaScript Promise is rejected but there is no rejection handler to deal with the rejection.

1
2
3
4
5
6
7
process.on('unhandledRejection', error => {
// Will print "unhandledRejection err is not defined"
console.log('unhandledRejection', error.message);
});
new Promise((_, reject) => reject(new Error('woops')))
.catch(error => { console.log('caught', err.message); });

此时执行后,就没有 UnhandledPromiseRejectionWarning 的警告输出了,只输出:unhandledRejection err is not defined

如果我们不想监听 unhandledRejection 事件,也不想看到 UnhandledPromiseRejectionWarning 的警告输出,怎么办呢?

1
2
new Promise((_, reject) => reject(new Error('woops')))
.catch(new Function());

我们可以在 .catch() 中传入一个空函数,假装对 rejection 进行了处理,这样也没有触发 unhandledRejection 事件

Async/Await

关于 Async/Await,可以参考文章:ES7 中的 async await,在这篇文章中详细介绍了 Async/Await 并且和 Promise 进行了对比,Async/Await 在处理异步操作上的优势更明显。

1
2
3
4
5
6
7
8
9
10
11
12
13
async function test() {
// No unhandled rejection!
await Promise.reject(new Error('test'));
}
test();
// 输出:
// (node:54358) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): Error: test
// (node:54358) [DEP0018] 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.
test().catch(error => console.log(error.message));
// 输出:
// test

async 异步函数返回的是 Promise,所以执行异步函数后,统一需要用 .catch() 对可能出现的 rejection 进行捕捉处理,否则统一也是会出现 UnhandledPromiseRejectionWarning 的警告

解决问题

最后解决一下文章开头的问题:构建任务中 html 解析错误,出现了一个 Unhandled Rejection,所以我们可以添加一个 unhandledRejection 事件监听,直接退出:

1
2
3
4
process.on('unhandledRejection', error => {
console.error('unhandledRejection', error);
process.exit(1) // To exit with a 'failure' code
});

参考链接

Unhandled Promise Rejections in Node.js