【问题标题】:JS event loop priorityJS事件循环优先级
【发布时间】:2022-03-29 13:53:21
【问题描述】:

我在 Nodejs 上运行此代码,我预计会出现错误,但它确实有效! 如果我将 2500 毫秒设置为 setTimeout 我得到一个错误,这是正常的。 有人在这里为我解释吗? 为什么我会先看到IIFE 登录?

我在浏览器上运行这段代码,结果如我所料。

const data = [{}]
const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([1,2,3,4]);
    }, 3000);
  });

console.log('hiiii');

setTimeout(() => {
    data[0].map(e => console.log('to ',e))
  }, 2999);
(async() => {
     data[0] = await myPromise
     data[0].map(e => console.log('iife ',e))
    }
)()

console.log('byeeeee')

下面的代码是我在控制台上看到的

hiiii
byeeeee
iife 1
iife 2
iife 3
iife 4
to 1
to 2
to 3
to 4

【问题讨论】:

  • 错误是什么?
  • 旁注:不要使用.map() 来迭代集合。这就是.forEach() 的用途。
  • 它应该抛出一个错误,说 data[0].map 不是一个函数,因为 data[0] 是一个对象
  • 我的主要问题是关于事件循环以及如何迭代对象!
  • @Ivar - 我想说不要将.forEach() 用作常规的for/of 循环,它可以为您提供更多的灵活性,并且不会一直创建额外的函数范围。

标签: javascript node.js asynchronous promise event-loop


【解决方案1】:

如果第 10 行恰好在第 2 行之后执行至少 1 毫秒,那么它的回调将排队运行 使用 resolve() 的回调(因此在“iife”日志记录之后,这发生在 resolve()) 之后。

解决方案是将第二个 setTimeout() 移到第一个之前,以确保它们的回调按预期顺序排队。所以,例如,这段代码:

const data = [[99]];
setTimeout(() => {
    data[0].forEach(e => console.log('to ',e))
  }, 2999);
const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([1,2,3,4]);
    }, 3000);
  });

console.log('hiiii');

(async() => {
     data[0] = await myPromise
     data[0].forEach(e => console.log('iife ',e))
    }
)();

console.log('byeeeee');

可靠地产生这个结果:

hiiii
byeeeee
to  99
iife  1
iife  2
iife  3
iife  4

事件循环的逻辑是,调用 setTimeout 会将其回调排队,以便在当前时间 x 毫秒后变为可运行。一旦经过了这段时间,它就必须等待当前正在执行的任何任务完成,然后等待在它前面排队的任何其他任务,然后才能真正运行。

不确定您要做什么,但您永远不会让“to 1”行出现在“iife 4”行之前,因为从设置数据 [0] 到记录“iife 4”的所有事情都会发生在单个同步的代码块中。

【讨论】:

  • 为什么我的回答被否决了?这里的问题完全是关于计时器创建的相对时间。我只是注意到,经过一些实验,我发现在我的硬件上,超时值为 2991 而不是 2999,上面的代码有时会成功,有时会在 data[0].map 上抛出预期的错误。添加当前毫秒的控制台日志记录表明,两次 setTimeout 调用之间通常需要大约 10 毫秒。这就是导致 OP 出现意外结果的原因。
  • 两次 setTimeout 调用之间有 10 毫秒?那肯定是加载控制台 API 的时候了,在此之前尝试进行一次热身调用。但是,您不需要在两个调用之间有一个实际的完整毫秒,第一个在例如 0.999 毫秒和第二个在 1.001 毫秒完成就足够了,以获得相同的结果。
【解决方案2】:

与异步 JavaScript 相关的问题只能通过研究事件循环来回答。

异步 JavaScript 如何工作?

  • 调用堆栈执行函数。 Web API 是异步操作(获取请求、承诺、计时器)及其回调等待完成的地方。
  • 任务队列(也称为 macrostasks)是一个 FIFO(先进先出)结构,用于保存准备执行的异步操作的回调。
  • 作业队列(也称为微任务)是一个 FIFO(先进先出)结构,其中包含准备好执行的承诺的回调。
  • 最后,事件循环永久监控调用堆栈是否为空。如果调用堆栈为空,则事件循环会查看作业队列或任务队列,并将任何准备好执行的回调出列到调用堆栈中。

举个例子:

setTimeout(function timeout() 
{
    console.log('Set timeout finished!');
}, 0);

Promise.resolve(1).then(function resolve() 
{
   console.log('Resolved!');
});

立即解决的承诺在立即超时之前处理。由于事件循环优先于任务队列(存储超时的 setTimeout() 回调)中的任务,从作业队列(存储已履行的承诺的回调)中出列作业。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多