3

我正在研究 JS 中的异步行为,并且大部分情况下进展顺利。我了解执行代码的同步方式,JS的单线程以及诸如setTimeout内部的回调如何由Web浏览器API定时,然后添加到任务队列中。

事件循环会不断地检查调用栈,只有当它为空时(所有同步代码都已执行),它才会取走已经在任务队列中排队的函数。将它们推回调用堆栈并执行。

这很简单,这就是下面代码的原因:

console.log('start');
setTimeout(() => console.log('timeout'), 0);
console.log('end');

会输出start, end, timeout

现在,当我开始阅读有关 Promise 的内容时,我了解到它们比常规异步代码(例如超时、间隔、事件侦听器)具有更高的优先级,而是被放置在作业队列/微任务队列中。事件循环将首先优先考虑该队列并运行所有作业,直到耗尽,然后再进入任务队列。

这仍然是有道理的,可以通过运行看到:

console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');

这输出start, end, promise, timeout. 同步代码执行,then回调从微任务队列推送到堆栈并执行,任务队列中的 setTimeout 回调任务被推送并执行。到目前为止一切都很好。

正如官方文档中所说,我可以围绕上面的例子来回想一下,promise 会立即同步地得到解决。如果我们要使用 new 关键字创建一个 Promise 并提供一个 executor 函数,也会发生同样的情况。该执行器函数将同步执行并解析该函数​​。因此,当遇到 then 时,它可以在已解决的 Promise 上异步运行。

console.log('start');

const p1 = new Promise(resolve => {
    console.log('promise 1 log');
    resolve('promise 1');
});

p1.then(msg => console.log(msg));

console.log('end');

上面的代码片段将输出start, promise 1 log, end, promise 1证明执行器同步运行。

这就是我对承诺感到困惑的地方,假设我们有以下代码:

console.log('start');

const p1 = new Promise(resolve => {
    console.log('promise 1 log');
    setTimeout(() => {
        resolve('promise 1');
    }, 0);
});

p1.then(msg => console.log(msg));

console.log('end');

这将导致start, promise 1 log, end, promise 1. 如果 executor 函数立即执行,这意味着其中的 setTimeout 将被放入任务队列以供稍后执行。据我了解,这意味着承诺现在仍在等待中。我们得到then方法和其中的回调。这将被放入作业队列中。其余的同步代码被执行,我们现在有空的调用堆栈。

据我了解,promise 回调现在将具有优先权,但它如何在仍未解决的 promise 下执行?Promise 应该只在其中的 setTimeout 被执行后才解决,它仍然位于任务队列中。我听说过,没有任何额外的说明,只有在承诺得到解决的情况下才会运行,从我的输出中我可以看到这是真的,但我不明白在这种情况下它是如何工作的。我唯一能想到的是一个异常或类似的东西,以及一个任务队列任务在微任务之前获得优先级。

这最终很长,所以我感谢任何花时间阅读和回答这个问题的人。我很想更好地了解任务队列、作业队列和事件循环,所以不要犹豫发布详细的答案!先感谢您。

4

3 回答 3

3

我们得到then方法和其中的回调。这将被放入作业队列中。

不,如果 Promise 仍处于未决状态,调用then不会立即将任何内容放入作业队列中。回调将安装在 Promise 上,以便稍后在 Promise 完成时执行,就像事件处理程序一样。只有当您调用时resolve(),它才会真正将其放入作业队列中。

这就像setTimeout你写的“ [the] 回调 [...] 将由 Web 浏览器 API 计时,然后添加到任务队列” - 它不会立即排队等待以某种方式等待的任务,但它等待然后将任务排队以执行回调。

于 2020-08-05T06:57:36.143 回答
1

... promise 回调现在将具有优先权...

只有当微任务队列中的任务存在时,它们才会优先于任务队列中的任务。

在示例中:

  • setTimout()在任务解决 Promise之前,没有微任务排队。
  • 任务和微任务不竞争。它们是连续的。
  • 任务队列和微任务队列(按此顺序)施加的延迟是相加的。

...但是如何执行仍未解决的承诺?

它没有。回调将.then()仅在 promise 完成后执行,并且该实现取决于放置在任务队列中的任务setTimeout()(即使延迟为零)。

于 2020-08-05T04:03:07.490 回答
0

JS 引擎有 1 个调用堆栈、宏任务队列、微任务队列和 web api。有关基本概念的更多信息:https ://stackoverflow.com/a/30910084/1779091

在 Promise 的情况下,promise 中的代码将运行,当调用 resolve 时,回调被添加到微队列中。

而 setTimeout 在 web api 中运行,一旦完成,它会将回调放入宏队列中。

console.log('start');
setTimeout(() => console.log('timeout'), 0);
console.log('end');
  • 打印start
  • 调用 setTimeout web api 并将调用传回给它
  • 此时 setTimeout 可能已经完成,也可能尚未完成。每当计时器耗尽时,回调将被放入宏队列
  • 打印end
  • 没有什么要执行的,所以检查队列是否有东西。这将输出timeout.
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
  • 打印start
  • 调用 setTimeout web api 并将调用传回给它
  • 此时 setTimeout 可能已经完成,也可能尚未完成。每当计时器耗尽时,回调将被放入宏队列
  • Promise 已解决,将回调(.then 之后的内容)放入微任务队列
  • 打印end
  • 没有什么要执行的,所以检查队列是否有东西。微任务队列的优先级高于宏任务队列。因此,第一个它将来自微任务的回调放入调用堆栈并打印promise,然后将来自宏任务队列的回调放入调用堆栈并打印timeout
console.log('start');

const p1 = new Promise(resolve => {
    console.log('promise 1 log');
    resolve('promise 1');
});

p1.then(msg => console.log(msg));

console.log('end');
  • 打印start
  • 创建承诺并将其分配给 p1
  • 运行打印的 p1 promise 1 log,然后解析将回调(.then 之后的东西)放入微任务队列
  • 打印end
  • 没有什么要执行的,所以检查队列是否有东西。来自 Micro 任务的回调被放入堆栈并打印promise 1
console.log('start');

const p1 = new Promise(resolve => {
    console.log('promise 1 log');
    setTimeout(() => {
        resolve('promise 1');
    }, 0);
});

p1.then(msg => console.log(msg));

console.log('end');
  • 打印start
  • 创建承诺并将其分配给 p1
  • 运行 print 的 p1 promise 1 log,然后调用调用 web api 的 setTimeout。此时 setTimeout 可能已经完成,也可能尚未完成。每当计时器耗尽时,回调将被放入宏队列
  • 打印end
  • 没有什么要执行的,所以检查队列是否有东西。宏任务的回调被放入堆栈,它将运行将回调(.then 之后的东西)放入微任务队列的解析。
  • 没有什么要执行的,所以检查队列是否有东西。来自 Micro 任务的回调被放入堆栈并打印promise 1
于 2022-02-18T11:51:42.250 回答