Promise 可以保持未处理状态多长时间而不触发“unhandledrejection”事件?

问题描述 投票:0回答:1

我想知道浏览器到底什么时候检查未处理的承诺? 我认为检查是在事件循环结束时执行的。但简单的实验表明事实恰恰相反。

如果我为

unhandledrejection
rejectionhandled
事件注册两个处理程序:

window.addEventListener('unhandledrejection', function(event) {
    console.log('UNHANDLED!'); 
    event.preventDefault();
});

window.addEventListener('rejectionhandled', function(event) {
    console.log('HANDLED!'); 
    event.preventDefault();
});

然后运行这样的代码:

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);

我预计事件

unhandledrejection
rejectionhandled
会被触发。但它们没有被触发(控制台中没有“未处理!”和“已处理!”消息)。

最奇怪的是,如果我将setTimeout中的0改为1毫秒,事件就会被触发:

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);

我在 HTML5 规范中找不到解释。

我尝试在多个浏览器的控制台(Chrome、Firefox、Safari)中运行提供的代码片段,并期望触发

unhandledrejection
rejectionhandled
事件。但他们没有。

有人可以解释这种行为,或提供一些有用的链接吗?

javascript promise settimeout event-loop unhandled-promise-rejection
1个回答
0
投票

要了解您提供的代码的行为,您应该首先了解以下概念:

  • 事件循环
  • 调用堆栈
  • 回调队列

这段摘录自Javascript运行时:JS引擎、事件循环、调用堆栈、执行上下文、堆和队列有助于理解上述概念。

异步事件发生后(定时器超时或网络请求完成),关联的回调函数被放入回调队列中。 当调用堆栈变空(意味着所有同步任务已完成)时,事件循环检查回调队列。 如果回调队列中有任何回调函数,事件循环将获取第一个回调函数并将其传递到调用堆栈执行。

接下来,您应该了解 JavaScript 计时器和

setTimeout
的工作原理。 这个答案应该可以帮助您理解计时器和
setTimeout

它是将这些回调[来自 setTimeout] 放入队列中,由单线程 JavaScript 引擎执行的环境。

现在,如果您了解了所有这些,那么这里是您提供的代码的演练。为了清楚起见,我命名了一些函数。

首先,我们添加事件监听器。它们将按照您的描述进行操作,但这里是 Promise 拒绝事件

unhandledrejection
rejectionhandled
的参考。

未处理的拒绝 当承诺被拒绝但没有可用的拒绝处理程序时发送。

拒绝处理 当处理程序附加到已导致未处理拒绝事件的拒绝承诺时发送。

window.addEventListener('unhandledrejection', function unhandledrejectionHandler(event) {
    console.log('UNHANDLED!'); 
    event.preventDefault();
});

window.addEventListener('rejectionhandled', function rejectionhandledHandler(event) {
    console.log('HANDLED!'); 
    event.preventDefault();
});

现在我就带大家了解一下延迟为

0
和延迟为
1
时的两种情况。

let promise = Promise.reject(new Error("Promise Failed!"));

let promiseCatchingCb = () => promise.catch(err => console.log('CAUGHT!'))

// Timer executes promiseCatchingCb immediately, adding it to the top of the call stack
setTimeout(promiseCatchingCb, 0);
  1. 承诺被拒绝
  2. promiseCatchingCb
    立即添加到回调队列并执行
  3. 窗口
    unhandledrejection
    事件侦听器从未触发,因为拒绝的承诺已被处理
let promise = Promise.reject(new Error("Promise Failed!"));

let promiseCatchingCb = () => promise.catch(err => console.log('CAUGHT!'))

// Timer executes promiseCatchingCb immediately, adding it to the top of the call stack
setTimeout(promiseCatchingCb, 1);
  1. 承诺被拒绝
  2. promiseCatchingCb
    不会立即添加到回调队列中,因为计时器仍在进行中(1ms)
  3. unhandledrejection
    事件被触发,并且
    unhandledrejectionHandler
    被添加到回调队列并执行
  4. promiseCatchingCb
    添加到回调队列并执行
  5. rejectionhandled
    事件被触发,并且
    rejectionhandledHandler
    被添加到回调队列并执行

简而言之,当您将

setTimeout
的延迟设置为 0 时,
unhandledrejection
事件就没有时间被触发。这与在回调队列上执行异步代码之前在调用堆栈上执行的同步代码不同,因为所讨论的不同函数都共享回调队列。

© www.soinside.com 2019 - 2024. All rights reserved.