NodeJS中unhandledRejection和rejectionHandled事件

注意:此文没有解释清楚rejectionHandled事件,只是过程记录

  《深入理解ES6》第十一章,全局的Promise拒绝处理一节中提到,有关Promise的其中一个最具争议的问题是,如果在没有拒绝处理程序的情况下拒绝一个Promise,那么不会提示失败信息,这是JavaScript语言中唯一一处没有强制报错的地方,然后Node提供了两个事件来处理这个问题,这两个事件在浏览器环境中也是可用的,在Node和浏览器中监听方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
process.on('unhandledRejection', (reason, promise) => {
// reason 拒绝原因,通常为一个错误对象
   // promise 导致此事件被触发的promise
});

process.on('rejectionHandled', promise => {
// promise 导致此事件被被触的promise
});

window.onunhandledrejection = (event) => {
event.type 事件名称
  event.reason 拒绝值
  event.promise 触发此事件的promise
};

window.onrejectionhandled = (event) => {
event.type 事件名称
  event.reason 拒绝值
  event.promise 触发此事件的promise
};

通过实际的代码测试发现,rejectionHandled事件和解释中所描述的触发条件不相符

先看书中解释及例子:

  • unhandledRejction 在一个事件循环中,当Promise被拒绝,并且没有提供拒绝处理程序时被调用
  • rejectionHandled 在一个事件循环后,当Promise被拒绝,并且没有提供拒绝处理程序时被调用
1
2
3
4
5
6
7
8
9
10
let rejected;
process.on('unhandledRejection', function(reason, promise) {
console.log(reason.message);
console.log(rejected === promise);
});
rejected = Promise.reject(new Error('Explosion!'));

// 此例容易理解,reject未被catch立即触发
// Explosion!
// true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let rejected;
process.on('rejectionHandled', function(promise) {
console.log(rejected === promise);
});
rejected = Promise.reject(new Error('Explosion!'));
setTimeout(() => {
rejected.catch(value => {
console.log(value.message);
});
}, 1000);

// 此例中rejectionHandled事件在拒绝处理程序最后被调用时触发,
// 如果在创建rejected之后直接添加拒绝处理程序,那么rejectionHandled事件不会被触发,
// 因为rejected创建的过程与拒绝处理程序的调用在同一个事件循环中,
// 此时rejectionHandled事件尚未生效
// UnhandledPromiseRejectionWarning: Error: Explosion!
// Explosion!
// true

经过验证之后发现 rejectionHandled 应该是在一个事件循环后,当Promise被拒绝,并且提供了处理程序时被调用,看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let rejectA = Promise.reject('A');
let rejectB = Promise.reject('B');
let rejectC = Promise.reject('C');
let rejectD = Promise.reject('D');

process.on('rejectionHandled', promise => {
console.log(3, promise);
});

setTimeout(() => {
rejectB.catch(d => {
console.log(2, d);
});
rejectC.then(d => {
console.log(5, d);
});
}, 1000);

rejectA.catch(d => {
console.log(4, d);
});

// 输出中一些node warning已经去掉
// 4 'A'
// 2 'B'
// 3 Promise { <rejected> 'B' }
// 3 Promise { <rejected> 'C' }

按照书中概念解释,有两个问题:

  • rejectD应该也会触发rejectionHandled事件,因为没有提供任何拒绝处理程序
  • 3和2的输出顺序问题,这个问题又涉及到事件循环和任务编排,Promise也有一个自己的任务队列,按照书中对rejectionHandled事件监听顺序的解释,立即catch不会触发事件,那下一轮循环触发事件时setTimeout中添加的catch的执行为什么会在事件之前呢?事件的监听在上一轮循环已经生效了,除非对于rejectionHandled事件来说,promise想要触发他,必须是在本身已经没有任何需要执行的代码情况下去触发这个事件

不知道对不对的解释:

  • 对于第一个问题,只要一个被拒绝的Promise添加了catch或者then,并且没有和rejectionHandled事件放在同一个事件循环中,就会触发rejectionHandled事件
  • 对于第二个问题,看下面的例子可以知道,promise上面的两个catch都执行后才触发事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let rejectedA;
rejectedA = Promise.reject('A');
process.on('rejectionHandled', function(promise) {
console.log(promise);
});
setTimeout(() => {
// rejectedA.then(value => {
// console.log(value);
// });
rejectedA.catch(value => {
console.log(value);
});
rejectedA.catch(value => {
console.log(value);
});
}, 1000);

// A
// A
// Promise { <rejected> 'A' }

推翻上面的说法:
Q:第一个循环中的catch才触发了rejectionHandled事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
let rejectedA;
rejectedA = Promise.reject('A');
process.on('rejectionHandled', function(promise) {
console.log(promise);
});
setTimeout(() => {
rejectedA.catch(value => {
console.log(value);
});
}, 1000);
setTimeout(() => {
rejectedA.catch(value => {
console.log(value);
});
}, 3000);

// A
// Promise { <rejected> 'A' }
// A


let rejectedA;
rejectedA = Promise.reject('A');
process.on('rejectionHandled', function(promise) {
console.log(promise);
});
setTimeout(() => {
rejectedA.then(value => {
console.log(value);
});
}, 1000);
setTimeout(() => {
rejectedA.catch(value => {
console.log(value);
});
}, 3000);

// Promise { <rejected> 'A' }
// A

Q:为什么rejectionHandled事件没有被触发?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let rejectedA;
setTimeout(() => {
rejectedA = Promise.reject('A');
rejectedA.catch(value => {
console.log(value);
});
process.on('rejectionHandled', function(promise) {
console.log(promise);
});
}, 1000);
setTimeout(() => {
rejectedA.catch(value => {
console.log(value);
});
}, 3000);

// A
// A

结论:搞了一下午,解释不清楚,庸人自扰

各种链接:

任务编排事件循环相关文章: