I am wondering when exactly do browsers check for unhandled promises? I thought that check is performed in the end of event loop tick. But simple experiment shows the opposite.
If I register two handlers for unhandledrejection and rejectionhandled events:
window.addEventListener('unhandledrejection', function(event) {
console.log('UNHANDLED!');
event.preventDefault();
});
window.addEventListener('rejectionhandled', function(event) {
console.log('HANDLED!');
event.preventDefault();
});
And then run such code:
let promise = Promise.reject(new Error("Promise Failed!"));
// Plan handling in next tick,
// in my understanding it SHOULD happen AFTER the unhandledrejection will be triggered
setTimeout(() => promise.catch(err => console.log('CAUGHT!')), 0);
I expect events unhandledrejection and rejectionhandled to be triggered. But they are not triggered (there is no 'UNHANDLED!' and 'HANDLED!' messages in console).
The most strange is that if I change 0 to 1 ms in setTimeout, the events are triggered:
let promise = Promise.reject(new Error("Promise Failed!"));
// Plan handling after 1 ms,
// REALLY happens AFTER the unhandledrejection is triggered
setTimeout(() => promise.catch(err => console.log('CAUGHT!')), 1);
I could not find an explanation in HTML5 spec.
I tried to run provided snippets in several browsers' consoles (Chrome, Firefox, Safari), and expected unhandledrejection and rejectionhandled events to be triggered. But they did not.
Could someone explain such behaviour, or provide some useful links?
To understand the behavior of the code you provided, you should first understand the following concepts:
This excerpt from Javascript Runtime: JS Engine, Event Loop, Call Stack, Execution Contexts, Heap, and Queues is helpful to understand the above concepts.
Next, you should understand how JavaScript Timers and
setTimeoutwork. This answer should help you understand Timers andsetTimeout.Now if you understand all that, here is a walkthrough of the code you provided. I have named some of the functions for clarity.
First, we add the event listeners. They will act as you described, but here is a reference for the Promise rejection events
unhandledrejectionandrejectionhandled.Now I will take you through the two cases when the delay is
0and when the delay is1.promiseCatchingCbis immediately added to callback queue and executedunhandledrejectionevent listener never triggered because rejected promise is already handledpromiseCatchingCbis not immediately added to the callback queue because timer is still ongoing (1ms)unhandledrejectionevent is fired andunhandledrejectionHandleris added to the callback queue and executedpromiseCatchingCbis added to the callback queue and executedrejectionhandledevent is fired andrejectionhandledHandleris added to the callback queue and executedIn short, when you set the delay of
setTimeoutto be 0, there isn't any time for theunhandledrejectionevent to be fired. This isn't the same as synchronous code executing on the callstack happening before asynchronous code executing on the callback queue because the different functions in question are all sharing the callback queue.