我最近观看了 Will Sentence 关于异步 javascript 的研讨会视频,我无法理解微任务队列是否始终优先于宏任务队列。
function display(data) {console.log(data)}
function printHello() {console.log(“Hello”);}
function blockFor300ms() {/* blocks js thread for 300ms with long for loop */}
setTimeout(printHello, 0);
const futureData = fetch('https://twitter.com/will/tweets/1')
futureData.then(display)
blockFor300ms()
console.log(“Me first!”);
在此示例中,Will 解释说,
printhello
被添加到宏任务(回调)队列中,然后 fetch
请求被执行,并假设在代码执行的 201 毫秒时,display
回调被添加到微任务队列中。与此同时,blockFor300ms
已阻止代码 300 毫秒。
在大约 301 毫秒时,控制台记录了 Me first!
,然后由于微任务队列的优先级高于宏任务队列,因此提取请求中的 data
被记录,最后是 setTimeout 的 Hello
。所以console.logs的顺序:
1. console.log("Me first!")
2. console.log(data) // data from fetch request
3. console.log("Hello")
我尝试在各种环境(Chrome、Firefox、Safari、node)中使用各种拦截器功能(具有不同的持续时间)执行类似的代码示例,我总是得到:
1. synchronous code console.log("Me first!")
2. console.log from the setTimeout
3. console.log with data from fetch request .then
我想结果可能取决于获取请求何时得到解决以及数据何时被接收,但我也尝试过阻塞代码超过 10 或 20 秒,结果总是先 setTimeout 然后获取。这是怎么回事?这背后的原因是什么?
这是我测试过的代码:
function display(data) {console.log("Fetched data:", data);}
function printHello() {console.log("Hello")}
function blockForDuration(duration) {
const startTime = Date.now();
while (Date.now() - startTime < duration) {}
}
setTimeout(printHello, 0);
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(display)
.catch(error => console.error("Fetch error:", error));
blockForDuration(10000);
console.log("Me first!");
/*
Console log order
1. "Me first!"
2. "Hello"
3. "Fetched data:", data
*/
我认为如果你用
async
和 await
编写代码会更清楚,因为更容易“介于”发生的事情之间:
!async function() {
function display(data) {console.log("Fetched data:", data);}
function printHello() {console.log("Hello")}
function blockForDuration(duration) {
const startTime = Date.now();
while (Date.now() - startTime < duration) {}
}
setTimeout(printHello, 0);
const p = fetch('http://jsonplaceholder.typicode.com/posts/1');
console.log("back from fetch with promise");
const pr = await p;
console.log("fetch initial promise resolved");
const j = await pr.json();
console.log("have JSON");
display(j);
blockForDuration(10000);
console.log("Me first!");
}();
在此版本中,有一条日志消息 after
fetch()
返回,但 before 等待承诺。请注意,fetch()
请求需要花费一些时间,但 Promise 几乎立即返回,因此这是记录的第一件事。
记录的下一个内容是来自
setTimeout()
的“Hello”,它在 fetch()
开始执行其实际工作并且函数挂起之后发生的宏任务中运行。这就是原文中第一次调用 .then()
时发生的情况。因此,原始中的 .then()
回调在微任务中运行,但它在不同的宏任务之后的微任务中运行;基本上是 HTTP 请求完成时。这是一个异步过程,因此 .json()
调用也是如此。 Promise 解析位于它们自己的宏任务上(当 .then()
实际运行并将回调排队时);这是在微任务中运行的回调。
请注意,由于
await
的性质,await
版本会记录“我优先”消息 last。该代码实际上位于
.then()
中,是在其他内容之后合成的。您可以重新排列 await
版本,使其更像原始版本。
这些东西很有趣,而且非常值得了解,但我个人的建议是避免根据任务机制如何交互的小细节做出重大的架构决策。我不会说在某些情况下它可能非常重要,但在大多数日常网络编程中,它只不过是一个有趣的细节。