【问题标题】:Callback not getting executed when I expect it回调没有在我期望的时候被执行
【发布时间】:2018-02-14 03:34:45
【问题描述】:

我有以下使用 fetch 的代码。据我了解,在履行承诺之前不会调用回调函数。因此,我期望回调函数在处理其他事情(例如 for 循环)的过程中执行。但是,它并没有达到我的预期。我的代码如下:

console.log("Before fetch")
fetch('https://example.com/data')
  .then(function(response){
    console.log("In first then")
    return response.json()
  })
  .then(function(json){
    console.log("In second then")
    console.log(json)
  })
  .catch(function(error){
    console.log("An error has occured")
    console.log(error)

  })
console.log("After fetch")
for(let i = 0; i < 1000000; i++){
   if (i % 10000 == 0)
      console.log(i)
}

console.log("The End")

不是在实现承诺时立即运行回调,而是在激活回调函数之前等待我的所有其余代码处理完毕。这是为什么呢?

我的代码输出如下所示:

Before fetch
After fetch
0
10000
.
.
.
970000
980000
990000
The End
In first then
In second then

但是,我预计最后两行会出现在此之前的某个地方。这里发生了什么?如何更改我的代码以反映实际兑现承诺的时间?

【问题讨论】:

  • 1M 操作对于现在的 CPU 来说不算什么,你确定你的请求只需要很少的时间吗(这里以毫秒为单位)。
  • 你指的是哪个回调?也许我遗漏了一些东西,但是在您的代码中,for 循环与您的承诺链是分开的......正如@NiVeR 所说,它似乎工作正常。
  • fetch.whatever 之后的所有内容。将在 fetch 开始后立即运行。 then 和 catch 块与你的 for 循环并行运行
  • 您可以使用async/await 强制执行任务的顺序
  • 我还建议观看这个关于事件循环的精彩解释youtube.com/watch?v=8aGhZQkoFbQ

标签: javascript callback promise


【解决方案1】:

这里的关键是您之后运行的 for 循环是一个长的、同步的代码块。这就是 JavaScript 中不推荐/不推荐使用同步 API 的原因,因为它们会阻止所有异步回调执行直到完成。 JavaScript 不是多线程的,它没有 C 中的中断之类的概念,因此如果线程正在执行一个大循环,那么在该循环完成之前,其他任何东西都没有机会运行。

在 Node.js 中,child_process API 允许您运行守护进程,而用于浏览器的 Web Worker API 允许并发进程并行运行,这两者都使用基于事件的序列化消息在线程之间进行通信,但是除此之外,以上段落中的所有内容都普遍适用于 JavaScript。

一般来说,分解长同步进程的一种可能解决方案是批处理。使用 Promise,您可以像这样重写 for 循环:

(async () => {
  for(let i = 0; i < 100000; i++){
     if (i % 10000 == 0) {
        console.log(i);
        // release control for minimum of 4 ms
        await new Promise(resolve => { setTimeout(resolve, 0); });
     }
  }
})().then(() => {
  console.log("The End");
});

setTimeout(() => { console.log('Can interrupt loop'); }, 1);

最少 4 毫秒的原因:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified

【讨论】:

  • 是否可以通过使用setTimeout(function() { }, 0); 将执行置于末尾来“降低”调用堆栈中的迭代优先级?解决时优先履行承诺?
  • @lkroneman 仅当在 setTimeout 延迟完成时承诺已经解决。回调队列是先进先出的,所以如果它们还没有解决,它们的回调将在 setTimeout 回调之后插入,从而导致它们被延迟到 for 循环完成。
【解决方案2】:

无论您的承诺多快兑现。回调添加到 Event Loop 并在所有同步任务完成后调用。在此示例中,同步任务是 for 循环。您可以使用setTimeout0ms 尝试事件,它也将在循环后工作。记住 JS 是单线程的,不支持并行任务。

参考: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

【讨论】:

    【解决方案3】:

    无法保证回调何时执行。 for 循环只需要很少的处理时间,因此 JS 引擎可能只是决定等到它结束后再完成回调函数。除非您将它们链接为回调,否则也没有特定的方法可以强制执行特定的函数顺序。

    【讨论】:

    • 当前正在执行其他 javascript(在本例中为 for 循环)时,不可能调用回调。
    猜你喜欢
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多