【问题标题】:Why are .catch callbacks executed at the end of the microtask queue?为什么 .catch 回调在微任务队列的末尾执行?
【发布时间】:2019-12-10 15:55:23
【问题描述】:

我在 HTML 文件中有以下代码:

const fooBar = function(resolve, reject) {
	let flag = (Math.round(Math.random() * 10) % 2);
	if(flag)
		resolve({ "value": "foo", "rand": Math.random() });
	else
		reject({ "value": "bar", "rand": Math.random() });
};
const fooBarSuccess1 = function(value) {
	console.log("Success 1:" + JSON.stringify(value));
};
const fooBarFailure1 = function(value) {
	console.log("Failure 1:" + JSON.stringify(value));
};
const fooBarSuccess2 = function(value) {
	console.log("Success 2:" + JSON.stringify(value));
};
const fooBarFailure2 = function(value) {
	console.log("Failure 2:" + JSON.stringify(value));
};
new Promise(fooBar).then(fooBarSuccess1).catch(fooBarFailure1);
new Promise(fooBar).then(fooBarSuccess2, fooBarFailure2);
console.log("Before setting MicroTask.");
setTimeout(() => console.log("This Timeout was set before the MicroTask!"));
queueMicrotask(() => console.log("From MicroTask!"));
console.log("After setting MicroTask.");

JSFiddle

当 Promise 被拒绝时,fooBarFailure1 在微任务队列的末尾执行,所以你可能会得到以下输出:

在设置 MicroTask 之前。 设置微任务后。 成功2:{"value":"foo","rand":0.3675094508130746} 来自微任务! 失败1:{"value":"bar","rand":0.6828171208953322} 此超时设置在 MicroTask 之前!

但是,不应该在queueMicrotask 内部的代码执行之前调用它吗?我没有看到fooBarFailure2 有任何此类问题。它按预期的顺序执行。结果在 Firefox 71 和 Google Chrome 78 中是一样的。谁能解释这里发生了什么?

【问题讨论】:

  • 旁注:如果你想要一个在 truefalse 之间分割为 50/50 的标志,如果你愿意,你可以比上面更简单一点:let flag = Math.random() < 0.5 .

标签: javascript ecmascript-6 es6-promise chaining


【解决方案1】:

不同之处在于fooBarFailure1fooBarFailure2 更远离根promise(来自new Promise 的那个)。 fooBarFailure1 没有连接到根承诺,它连接到由.then(fooBarSuccess1) 创建的承诺:

new Promise(fooBar).then(fooBarSuccess1).catch(fooBarFailure1);

相比之下,fooBarSuccess2fooBarFailure2 附加到根承诺:

new Promise(fooBar).then(fooBarSuccess2, fooBarFailure2);

fooBarFailure1 之前的链中有一个内部拒绝处理程序,但 fooBarFailure2 是直接挂钩的。这就是导致额外异步“滴答”的原因。

我们只看失败的例子,因为它简化了事情:

const success = function(value) {
    console.log("This never happens");
};
const fooBarFailure1 = function(value) {
    console.log("Failure 1");
};
const fooBarFailure2 = function(value) {
    console.log("Failure 2");
};
Promise.reject().then(success).catch(fooBarFailure1);
Promise.reject().then(success, fooBarFailure2);
console.log("Before setting MicroTask.");
setTimeout(() => console.log("This Timeout was set before the MicroTask!"));
queueMicrotask(() => console.log("From MicroTask!"));
console.log("After setting MicroTask.");

输出是:

在设置 MicroTask 之前。 设置微任务后。 失败2 来自微任务! 失败1 此超时设置在 MicroTask 之前!

原因如下:

  • Promise.reject() 在这两种情况下都返回一个被拒绝的承诺。
  • Promise.reject().then(success).catch(fooBarFailure1);
    • .then(success) 创建一个新的承诺并连接履行和拒绝处理程序;拒绝处理程序是内部的,只是传递拒绝原因,因为没有提供拒绝处理程序。
    • .catch(fooBarFailure1) 在来自 then 的 Promise 上连接一个拒绝处理程序。
  • 由于 promise 被拒绝,它使用微任务调用附加到它的拒绝处理程序。
  • Promise.reject().then(success, fooBarFailure2);:
    • then 将履行处理程序 (success) 和拒绝处理程序 (fooBarFailure2) 连接到来自 Promise.reject() 的承诺
  • 由于 promise 被拒绝,它使用微任务调用附加到它的拒绝处理程序。
  • "Before setting MicroTask." 已记录。
  • setTimeout 将其任务排队
  • queueMicrotask 将其微任务排队
  • "After setting MicroTask." 已记录。
  • 任务调度到此结束,微任务处理开始
    • 第一个微任务是处理来自Promise.reject().then(success).catch(fooBarFailure1); 的拒绝:它拒绝了then 创建的承诺,排队一个微任务以在返回的承诺then 上调用拒绝处理程序。
    • 第二个微任务是处理来自Promise.reject().then(success, fooBarFailure2); 的拒绝:拒绝承诺,调用fooBarFailure2
      • "Failure 2" 已记录。
    • 第三个微任务是来自queueMicrotask 的微任务,它运行。
      • "From MicroTask!" 已记录。
    • 上面安排的对fooBarFailure1 的微任务调用运行。
      • "Failure 1" 已记录。
  • 下一个任务运行,调用定时器回调
    • "This Timeout was set before the MicroTask!" 已记录。

【讨论】:

    猜你喜欢
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多