The other day I came across the following peace of code:
let promise = Promise.reject(new Error('Promise Failed!')); // 1
setTimeout(() => promise.catch(err => alert('caught')), 1000); // 2
// 3
So I was quite surprised to find out the error was caught in the handler registered one second after the error had occurred.
Now, let me explain step by step the way I perceive this, so you could correct me and tell me where I'm wrong:
- During the current macrotask, which is executing this whole script, a promise rejects (1) and since there is no registered rejection handler the control sequentially moves on to the next line.
- We call
setTimeout
passing it a callback function where rejection handler is to be registered (2). But this callback will be scheduled only after a second delay. Furthermore, it will be executed within a separate task. - The execution is on line 3. The current macrotask is done, both stack and task queue are empty. All that's left is to wait for a given delay of 1 second.
- One second timeout is complete. Our callback gets to the task queue and right away it is being pushed to the stack which effectively runs it.
- During the execution of the callback function, which is part of a new task,
.catch
handler is registered for the promise rejected one second ago, which took place during the previous and already finished task. Nevertheless, it successfully catches the error.
Does this mean that all this time the error had been somewhere in memory waiting for a chance that maybe .catch
handler would be registered later? But what if it had never happened and the number of rejected promises had been a lot more? Will unhandled errors remain to 'hang' in memory waiting for its handler to be registered?