通过承诺、任务和作业队列了解异步 JS

Understanding async JS with promises, task and job queue

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

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

这非常简单明了,这就是以下代码的原因:

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

会输出start, end, timeout.

现在,当我开始阅读 promises 时,我了解到它们比常规异步代码(如超时、间隔、事件监听器)具有更高的优先级,而是将被放入作业 queue/microtask 队列中。事件循环将首先确定该队列和 运行 所有作业的优先级,直到耗尽,然后再转到任务队列。

这个还是有道理的,可以被运行ning看到:

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 关键字创建一个承诺并提供一个执行函数,也会发生同样的情况。该执行器函数将同步执行并解析该函数​​。因此,当遇到 then 时,它可以 运行 异步处理已解决的承诺。

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 证明执行者 运行 是同步的。

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

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。如果执行器函数立即执行,则意味着其中的 setTimeout 将被放入任务队列以供稍后执行。据我了解,这意味着该承诺目前仍在等待中。我们得到 then 方法和其中的回调。这将被放入作业队列。其余的同步代码已执行,我们现在有了空的调用堆栈。

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

这篇文章很长,所以我感谢大家花时间阅读并回答这个问题。我很想更好地理解任务队列、作业队列和事件循环,所以请不要犹豫,发布详细的答案!提前谢谢你。

... the promise callback will have the priority now ...

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

在示例中:

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

... but how can it execute with the still unresolved promised?

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

We get to the then method and the callback within it. This will be put in the job queue.

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

这就像 setTimeout 一样工作,您在其中写道“[the] 回调 […] 将由 Web 浏览器计时 API,并且 later on added to the task queue" - 它不会立即将某个等待的任务加入队列,而是等待 然后 将任务加入队列执行回调。

JS 引擎有 1 个调用堆栈、宏任务队列、微任务队列和 Web api。更多基本概念:

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

而网络中的 setTimeout 运行s api 一旦完成,它会将回调放入宏队列中。

console.log('start');
setTimeout(() => console.log('timeout'), 0);
console.log('end');
  • 打印start
  • 调用 setTimeout 网络 api 并将调用传递回它
  • 此时 setTimeout 可能已完成也可能未完成。每当定时器耗尽时,回调将被放入宏队列
  • 打印end
  • 没有什么可执行的了,所以请检查队列中是否有东西。这将输出 timeout.
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
  • 打印start
  • 调用 setTimeout 网络 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
  • 运行 打印promise 1 log 的p1,然后解析将回调(.then 之后的东西)放入微任务队列
  • 打印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
  • 创建承诺并将其分配给 p1
  • 运行 打印 promise 1 log 的 p1,然后调用调用 web api 的 setTimeout。此时 setTimeout 可能已经完成,也可能还没有完成。每当定时器耗尽时,回调将被放入宏队列
  • 打印end
  • 没有什么可执行的了,所以请检查队列中是否有东西。来自宏任务的回调被放入堆栈,它将 运行 将回调(.then 之后的东西)放入微任务队列的解析。
  • 没有什么可执行的了,所以请检查队列中是否有东西。来自微任务的回调被放入堆栈,它将打印 promise 1