【问题标题】:Understanding async JS with promises, task and job queue了解带有承诺、任务和作业队列的异步 JS
【发布时间】:2020-11-25 04:52:43
【问题描述】:

我正在研究 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 回调任务被推入并执行。到目前为止一切顺利。

如官方文档所述,我可以围绕上面的示例展开,即承诺立即同步解决。如果我们要使用 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');

上面的sn-p会输出start, promise 1 log, end, promise 1证明executor同步运行。

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

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 被执行后才解决,它仍然位于任务队列中。我听说过,没有任何额外的说明,只有在承诺得到解决的情况下才会运行,从我的输出中我可以看到这是真的,但我不明白在这种情况下会如何工作。我唯一能想到的是一个异常或类似的东西,以及一个任务队列任务在微任务之前获得优先级。

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

【问题讨论】:

  • 并且,请记住,在大多数情况下,不同类型的异步事件的这种相对优先级并不是您在编码中应该依赖的东西,因为无论如何,所有这些都是异步竞赛,时间不可预测。如果您希望在另一个响应之前处理特定响应,则应编写代码以实际执行此操作,而无需考虑此级别的时序细节,方法是强制以您编写代码的方式进行某些排序。
  • 这很有道理,谢谢提醒。我对这些异步概念仍然很陌生,并且想很好地理解它们。我只能想象,当处理可能需要不可预测的时间的数据或函数时,最好以你想要的方式强制它们的顺序。

标签: javascript asynchronous promise event-loop job-queue


【解决方案1】:

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

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

在示例中:

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

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

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

【讨论】:

  • 在任务队列和作业队列中都有一个空的调用堆栈和任务,事件循环将首先从作业队列中提取函数推送到调用堆栈,是不是正确的?这就是我所说的优先级。但是正如您所说, then() 取决于放置在任务队列中的任务。这一事实是否会使事件循环开始并首先从任务队列中推送该任务,然后再返回放置在作业队列中的 then 回调?
  • 您的误解似乎是promise.then(callback) 将任务放入微任务队列中。它没有。相反,它会暂停callback,直到满足promise。只有这样它才会进入微任务队列。
  • 哦,这很有意义。通过在超时内添加一个额外的日志,我看到超时 cb 实际上确实在我的 then cb 之前运行,正如你所说,那是因为没有微任务被拾取。然后我假设 resolve 函数会放置微任务,但你的回答澄清了这一点。
  • 哇!我以为我要花很长时间了 :-)
  • @KonstantinosPascal - Promise 在内部存储了一组侦听器,用于解析和拒绝通知。当且仅当 promise 被解决或拒绝时,适当的侦听器(回调)才会被插入到任务队列中,因此应该调用这些侦听器。它非常类似于 EventEmitter 上的事件侦听器,只是这些侦听器只能被调用一次(根据 Promise 设计规范)。
【解决方案2】:

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

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

这就像setTimeout 一样工作,您在其中写道“[the] 回调 [...] 将由 Web 浏览器 API 定时,并稍后添加到任务队列" - 它不会立即排队等待以某种方式等待的任务,但它会等待并然后将任务排队以执行回调。

【讨论】:

    猜你喜欢
    • 2018-11-03
    • 1970-01-01
    • 2017-10-22
    • 1970-01-01
    • 1970-01-01
    • 2015-08-21
    • 2017-07-18
    • 1970-01-01
    • 2013-01-18
    相关资源
    最近更新 更多