解释 setTimeout 和 catch 处理程序的执行顺序

Explain this order of execution of setTimeout and catch handlers

我对定时器函数、微任务和事件监听器的执行顺序有疑问:

let promise = Promise.reject("Err@!");

setTimeout(() => {
        promise.catch(er => console.log(er, "Caught !"));         // (1)
});

window.addEventListener('unhandledrejection', event => console.log("Unhandled Rejection !", event.reason));  //  (2)

// Output 
// >> Unhandled Rejection ! Err@!
// >> Err@! Caught !

在此,被拒绝的 promise 在被 setTimeout 中的 .catch() 捕获之前被 Unhandled Rejection 事件捕获,原因是:

An "unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue (https://javascript.info/microtask-queue#unhandled-rejection)

现在考虑另一种情况:

let promise = Promise.reject("Err@!");

setTimeout(() => {       // (1)  ,gets called first

    setTimeout(function () {  // (3) , gets called at last , (after (2))
        promise.catch(er => console.log("caught ", er  ))
    })

});

setTimeout(function () {      // (2) , gets called after (1)
    window.addEventListener('unhandledrejection', event => console.log("Unhandled Rejection !", event.reason));
})

// Output 
//>>  caught  Err@!

在此,promise 被嵌套 setTimeout 中的 catch 处理程序捕获,即使在第二个 setTimeout 中,微任务队列为空并且仍未处理被拒绝的 promise通过 window 事件侦听器...

为什么会这样?

当 promise 拒绝(未被处理)时,unhandledrejection 处理程序尚未附加,因此它不会处理该案例。您可以随时附加 .catch.then 处理程序,如果 Promise 已经 resolved/rejected,它们将立即触发(在微任务中)。

 // first example
 1) synchronous execution
  - promise rejected
  - timeout set 
  - unhandledrejection event handler attached
 2) microtasks
  - promise rejects
 3) macrotask
  - timeout triggers
    -> .catch handler gets attached
 4) microtasks
   .catch gets executed

 // second example
 1) synchronous execution
 - timers get attached
 - promise rejects
 2) microtasks
 - promise rejected
 3) macrotasks
 - timer 1 done
   - timer 3 started
 - timer 2 done 
    - unhandledrejection handler added