簡體   English   中英

事件循環更喜歡微任務隊列而不是回調隊列?

[英]event loop prefers microtask queue over callback queue?

我正在 JS 中測試異步代碼的概念。 在回調隊列和微任務隊列順序之間感到困惑。 每當 promise 對象得到解決時,履行方法 { then } 被推入微任務隊列,而瀏覽器定時器函數的回調(如 setTimeout)被推入回調隊列。 事件循環不斷檢查隊列,並在調用堆棧變空時將函數從隊列推入調用堆棧。 事件循環應該比普通回調隊列更喜歡微任務隊列,但在示例中: https ://jsfiddle.net/BHUPENDRA1011/2n89ftmp/ 否則會發生。

function display(data) {
    console.log('hi back from fetch');
}

function printHello() {
    console.log('hello');
}

function blockfor300ms() {
    for (let i = 0; i < 300; i++) {
        // just delaying logic
    }
}
// this sets the printHello function in callback queue { event loop queue }
setTimeout(printHello, 0);

const futureData = fetch('https://api.github.com/users/xiaotian/repos');
// after promise is resolved display function gets added into other queue : Microtask queue { job queue}
futureData.then(display);
// event loop gives prefrence to Microtask queue ( untill  its complete) 

blockfor300ms();
// which runs first 
console.log('Me first !')

預期產出

  • 我先 !
  • 嗨,從 fetch 回來
  • 你好

實際輸出:

  • 我先 !
  • 你好
  • 嗨,從 fetch 回來

請讓我知道這里的情況。

謝謝

雖然這是真的,但“kib”所說的是:

“你的函數 blockfor300ms 沒有阻塞線程足夠長的時間來獲取響應”

遺憾的是,這無關緊要,因為即使您在收到對 fetch 調用的響應之后才阻止執行,您仍然會看到相同的結果...... (請參閱下面的示例代碼片段,您可以使用警告框或非異步 XMLHttpRequest 調用的長循環,我收到了相同的結果)

不幸的是,fetch 並不像我發現的所有博客和資源所描述的那樣工作......其中指出

Fetch 會將它的承諾鏈添加到微任務隊列中,並在事件循環的下一個滴答的任何回調之前運行。

從下面的示例代碼中,似乎fetch 並沒有像其他人所描述的那樣簡單地將它的解析處理函數添加到微任務隊列中,因為正如 Lewis 所說,因為它需要網絡活動,所以它由網絡任務源處理。 但是,我不認為這是由於 printHello “阻塞”了網絡任務,因為在下面的示例代碼中,fetch 是在 printHello 之前觸發的,並且網絡響應也會在計時器完成之前發生。

正如您在下面的示例中所看到的,在收到 fetch 響應后很長時間(2000 毫秒),我將 printHello 延遲添加到任務隊列中,但是如果我們阻止代碼執行超過 2000 毫秒(以便仍在運行執行上下文)然后將首先打印“Hello”。 這意味着 fetch resolve() 處理程序不是簡單地添加到微任務隊列中,它會與其他承諾處理程序一起觸發。

那么,如果在計時器任務完成之前(在 2000 毫秒)收到響應並理論上將其添加到任務隊列中,為什么它仍然在回調之后被記錄? 好吧,我的猜測是計時器任務源的優先級必須高於網絡任務源的優先級。 因此,兩者都坐在他們的任務隊列中,但計時器任務隊列在網絡任務隊列之前觸發......

規格鏈接:

計時器任務源 - https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-task-source網絡任務源 - https://html.spec.whatwg.org/multipage /webappapis.html#networking-task-source

 function display(data){console.log("Fetch resolved!")} function printHello(){console.log("Callback Time")} function blockExecution() { console.log("Blocking execution..."); alert('Wait at least 2000ms before closing'); } const futureData = fetch('https://jsonplaceholder.typicode.com/todos/1'); futureData.then(response => response.json()).then(display); setTimeout(printHello, 2000); const p = new Promise( // this is called the "executor" (resolve, reject) => { console.log("I'm making a promise..."); resolve("Promise resolved!"); console.log("Promise is made..."); } ); p.then( // this is called the success handler result => console.log(result) ); blockExecution(); console.log("Execution ended!");

futureData實際上是一個 fetch 承諾,因此在調用fetch時絕對有一個網絡任務排隊進入任務隊列。 因此, printHello肯定會在網絡任務之前執行,因為它們都是任務。 並且只有在網絡任務的promise解決后,方法display才會被放入微任務隊列。 根據定義,微任務僅在每個任務結束時執行。 所以display會在網絡任務結束時調用printHello已經被調用之前很長一段時間。

如果你想display之前被稱為printHellofutureData只能排隊microtasks。 讓我們稍微修改一下您的示例。

 function display(data) { console.log('hi back from fetch'); } function printHello() { console.log('hello'); } let now = Date.now(); function executeFutureDataWithMicrotasksOnly() { // Execute microtasks continually in 300ms. return Promise.resolve().then(() => Date.now() - now < 300 && executeFutureDataWithMicrotasksOnly()); } function blockfor300ms() { for (let i = 0; i < 300; i++) { // just delaying logic } } // this sets the printHello function in callback queue { event loop queue } setTimeout(printHello, 0); const futureData = executeFutureDataWithMicrotasksOnly(); // after promise is resolved display function gets added into other queue : Microtask queue { job queue} futureData.then(display); // event loop gives prefrence to Microtask queue ( untill its complete) blockfor300ms(); // which runs first console.log('Me first !')

從上面的示例中可以看出,如果將fetch替換為只有微任務的方法,盡管fetchexecuteFutureDataWithMicrotasksOnly在相似的時間間隔內執行,但執行順序會按預期更改。 futureData不再對任務進行排隊時,包括display在內的所有微任務都會在當前正在執行的任務結束時執行,也就是任務printHello的前一個任務。

我認為問題來自這樣一個事實,即您的函數blockfor300ms沒有阻止線程足夠長的時間來獲取響應。 當事件循環看到它可以調用printHello時,作業隊列中不會有任何東西(還)。

使用可視化

這將幫助您更好地了解 javascript 的工作原理。

在這種情況下, fetch( Promise ) 花費的時間比setTimeout所以當事件循環周期運行時, fetch( Promise ) 仍在進行中, setTimeout首先執行,因為它花費的時間更少,並且從任務隊列中出來,它得到處理后,當 fetch( Promise ) 結束時,它會從微任務隊列中出來。

如果您將增加setTimeout時間,那么第一個 fetch( Promise ) 將首先發生,然后 setTimeout 將發生。 希望這能解決您的問題。

這似乎比我們想象的要容易:

如果在初始執行期間,微任務旋轉事件循環,則微任務有可能被移動到常規任務隊列。 HTML Living Standard #queue一個微任務

當循環在從任務隊列中選擇一個任務的過程中,它可以選擇執行以前排隊進入微任務隊列並且現在是任務隊列一部分的任務:

在這種情況下,下一步選擇的任務最初是一個微任務,但它作為旋轉事件循環的一部分被移動了。 HTML Living Standard #event 循環處理模型

旋轉循環的代碼是任何包含並行操作的代碼:

  1. 在平行下:
  1. 等到滿足條件目標。

HTML 生活標准 #spin-the-event-loop

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM