在以下代码中:
setTimeout(() => console.log("hello"), 0);
Promise.resolve('Success!')
.then(console.log)
我理解应该发生什么:
print hello
当时间为 0 时直接添加到回调队列print Success!
添加到回调队列如果我没记错的话,回调队列是FIFO。
但是代码输出是:
Success!
hello
什么解释?
有 2 个单独的队列用于处理回调。一个macro和一个micro队列。
setTimeout
将 macro 队列中的项目排入队列,同时承诺解析 - 到 micro 队列。当前正在执行的宏任务(在本例中为主脚本本身)同步执行,逐行执行,直到完成。完成后,循环将执行 microtask 队列中排队的所有内容,然后继续 macro 队列中的下一个项目(在您的情况下是从 console.log("hello")
中排队的 setTimeout
)。
基本上,流程是这样的:
宏任务队列:[],微任务队列:[].
setTimeout(() => console.log("hello"), 0);
遇到导致在宏任务队列中推送一个新项目。宏任务队列:[
console.log("hello")
],微任务队列:[].
Promise.resolve('Success!').then(console.log)
已阅读。 Promise 立即解析为 Success!
并且 console.log
回调被排入微任务队列。宏任务队列:[
console.log("hello")
],微任务队列:[console.log('Success!')
].
console.log('Success!')
从微任务队列中拉出并执行。宏任务队列:[
console.log("hello")
],微任务队列:[].
console.log("hello")
.宏任务队列:[],微任务队列:[].
console.log("hello")
后,它再次检查微任务队列中是否有任何东西。它是空的,所以它检查宏任务队列。它也是空的,所以所有排队的东西都被执行,脚本完成。不过,这是一个简化的解释,因为它可能会变得更加棘手。微任务队列通常主要处理 promise 回调,但您可以自己将代码入队。微任务队列中新添加的项目仍然会在下一个宏任务项目之前执行。此外,微任务可以将其他微任务排入队列,这可能导致处理微任务的无限循环。
一些有用的参考资源:
这里涉及到两个不同的队列:一个任务队列和一个微任务队列。
使用
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,回调函数仍将添加到 Web API(从调用堆栈中获取后)。 Web API 是您无法访问的线程;你可以只调用 Ajax、Timeout 和 DOM。
Promise.resolve 调度一个微任务,而 setTimeout 调度一个宏任务。微任务在运行下一个宏任务之前执行。
所以在你的例子中,
Promise.resolve('Success!').then(console.log);
将在 setTimout 之前执行,因为 promises 比事件循环堆栈中的 setTimeout 回调函数具有更高的优先级。