JavaScript 异步回调 - Promise 和 setTimeout [重复]

问题描述 投票:0回答:3

在以下代码中:

setTimeout(() => console.log("hello"), 0);

Promise.resolve('Success!')
  .then(console.log)

我理解应该发生什么:

  1. setTimeout 被调用 =>
    print hello
    当时间为 0 时直接添加到回调队列
  2. Promise.resolve =>
    print Success!
    添加到回调队列

如果我没记错的话,回调队列是FIFO

但是代码输出是:

Success!
hello

什么解释?

javascript promise event-loop
3个回答
12
投票

有 2 个单独的队列用于处理回调。一个macro和一个micro队列。

setTimeout
macro 队列中的项目排入队列,同时承诺解析 - 到 micro 队列。当前正在执行的宏任务(在本例中为主脚本本身)同步执行,逐行执行,直到完成。完成后,循环将执行 microtask 队列中排队的所有内容,然后继续 macro 队列中的下一个项目(在您的情况下是从
console.log("hello")
中排队的
setTimeout
)。

基本上,流程是这样的:

  1. 脚本开始执行。

宏任务队列:[],微任务队列:[].

  1. setTimeout(() => console.log("hello"), 0);
    遇到导致在宏任务队列中推送一个新项目。

宏任务队列:[

console.log("hello")
],微任务队列:[].

  1. Promise.resolve('Success!').then(console.log)
    已阅读。 Promise 立即解析为
    Success!
    并且
    console.log
    回调被排入微任务队列。

宏任务队列:[

console.log("hello")
],微任务队列:[
console.log('Success!')
].

  1. 脚本完成执行后,它会在继续执行宏队列中的下一个任务之前检查微任务队列中是否有内容。
  2. console.log('Success!')
    从微任务队列中拉出并执行。

宏任务队列:[

console.log("hello")
],微任务队列:[].

  1. 脚本再次检查微任务队列中是否还有其他内容。没有,所以它从宏任务队列中获取第一个可用任务并执行它,即 -
    console.log("hello")
    .

宏任务队列:[],微任务队列:[].

  1. 脚本执行完
    console.log("hello")
    后,它再次检查微任务队列中是否有任何东西。它是空的,所以它检查宏任务队列。它也是空的,所以所有排队的东西都被执行,脚本完成。

不过,这是一个简化的解释,因为它可能会变得更加棘手。微任务队列通常主要处理 promise 回调,但您可以自己将代码入队。微任务队列中新添加的项目仍然会在下一个宏任务项目之前执行。此外,微任务可以将其他微任务排入队列,这可能导致处理微任务的无限循环。

一些有用的参考资源:


10
投票

这里涉及到两个不同的队列:一个任务队列和一个微任务队列

使用

setTimeout
调度的回调函数被添加到 task queue 而使用 promises 调度的回调被添加到 microtask queue 或作业队列中。

一个微任务队列被处理:

  • 只要调用堆栈为空,每次回调后。
  • 在每个任务之后。

还要注意,如果微任务队列中的一个微任务排队另一个微任务,那么它也将在 before 处理任务队列中的任何东西之前被处理。换句话说,微任务队列将被处理直到它为空 before 处理任务队列中的下一个任务。

以下代码片段显示了一个示例:

setTimeout(() => console.log('hello'), 0);

Promise.resolve('first microtask')
  .then(res => {
    console.log(res);
    return 'second microtask';
  })
  .then(console.log);


在您的代码中,

setTimeout
的回调函数被添加到任务队列中,
Promise.resolve
将微任务排队到微任务队列中。该队列在脚本执行结束时进行处理。这就是为什么在“你好”之前记录“成功”的原因。

下图显示了代码的逐步执行:

进一步阅读的资源:


0
投票

即使超时为 0,回调函数仍将添加到 Web API(从调用堆栈中获取后)。 Web API 是您无法访问的线程;你可以只调用 Ajax、Timeout 和 DOM。

Promise.resolve 调度一个微任务,而 setTimeout 调度一个宏任务。微任务在运行下一个宏任务之前执行。

所以在你的例子中,

Promise.resolve('Success!').then(console.log);

将在 setTimout 之前执行,因为 promises 比事件循环堆栈中的 setTimeout 回调函数具有更高的优先级。

© www.soinside.com 2019 - 2024. All rights reserved.