Promise.then与rejectionhandled在事件循环中的执行顺序差异探究
事件循环中Promise.then与rejectionhandled的执行顺序差异解析
问题背景
在事件循环的下一次运行中,Promise.then与rejectionhandled的执行顺序存在明显差异,以下两个示例可直观呈现不同表现:
示例1:直接在unhandledrejection处理器中捕获拒绝,rejectionhandled未触发
var prom1 = new Promise(function(resolve, reject) { setTimeout(function() { console.log("Prom 1 executed"); reject("Rejected"); }, 2000); }); console.log("Before Prom 1"); window.addEventListener("rejectionhandled", function(event) { console.log("Handled Rejection"); // 从未触发 }); window.addEventListener("unhandledrejection", function(e) { console.log("unhandledrejection"); prom1.then(null, function(err) { console.log("Caught error"); }); });
此场景下rejectionhandled事件完全没有触发。
示例2:用setTimeout包裹捕获逻辑,rejectionhandled正常触发
var prom1 = new Promise(function(resolve, reject) { setTimeout(function() { console.log("Prom 1 executed"); reject("Rejected"); }, 2000); }); console.log("Before Prom 1"); window.addEventListener("rejectionhandled", function(event) { console.log("Handled Rejection"); // 正常触发 }); window.addEventListener("unhandledrejection", function(e) { console.log("unhandledrejection"); setTimeout(function() { prom1.then(null, function(err) { console.log("Caught error"); }); }, 0); });
此场景下rejectionhandled事件可以正常触发。
核心原因:事件循环的任务检测时机
两个示例的差异本质在于Promise拒绝的处理时机与浏览器的未处理拒绝检测周期是否匹配:
示例1的逻辑链:
- Promise被
reject后,浏览器会在当前宏任务结束前检测到未处理的拒绝,触发unhandledrejection事件。 - 在
unhandledrejection回调里直接调用prom1.then添加拒绝处理器,这个处理器会被加入当前宏任务末尾的微任务队列。 - 浏览器的未处理拒绝检测是批处理式的:同一事件循环周期内,Promise从"未处理拒绝"变为"已处理"时,浏览器不会立即触发
rejectionhandled——检测逻辑只会在下一个事件循环周期开始前执行。 - 由于拒绝处理器在当前周期内就完成了处理,等不到下一轮检测,所以
rejectionhandled不会被触发。
- Promise被
示例2的逻辑链:
setTimeout会把添加拒绝处理器的逻辑放到下一个宏任务队列中。- 当前宏任务结束后,浏览器完成本轮未处理拒绝的检测(已标记该Promise为"未处理"),进入下一个事件循环周期。
- 执行
setTimeout回调,添加拒绝处理器,此时Promise状态从"未处理拒绝"变为"已处理"。 - 当这个宏任务结束后,浏览器的下一轮未处理拒绝检测会发现状态变化,从而触发
rejectionhandled事件。
简单来说:rejectionhandled的触发需要满足"Promise拒绝在被标记为未处理之后,在另一个事件循环周期内被处理"的条件。如果在触发unhandledrejection的同一周期内就处理了拒绝,浏览器不会认为这是"后续被处理"的情况,也就不会触发rejectionhandled。
内容的提问来源于stack exchange,提问作者user31782
相关产品推荐
相关产品推荐

