這是一篇谷歌大神文章,寫得很是精彩。譯者想借此次翻譯深刻學習一下,因爲水平有限,英文好的同窗建議直接閱讀原文。
原文地址:Tasks, microtasks, queues and schedules
下面正文開始:html
首先看一段代碼:html5
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end');
打印順序是什麼?
正確答案是
script start, script end, promise1, promise2, setTimeout
可是在不一樣瀏覽器上的結果倒是讓人懵逼的。es6
Microsoft Edge, Firefox 40, iOS Safari 和 desktop Safari 8.0.8在promise1,promise2以前打印了setTimeout,--雖然看起來像競態條件。
但讓人懵逼的是Firefox 39 , Safari 8.0.7會打印出正確順序。
譯者注:譯者的Microsoft Edge 38.14393.2068.0,Firefox 59.0.2 版本會打印出正確順序,應該已經支持了吧,其餘瀏覽器未驗證。web
要理解這些你首先須要對事件循環機制處理宏任務和微任務的方式有了解。
若是是第一次接觸信息量會有點大。深呼吸……chrome
每一個線程都會有它本身的event loop(事件循環),因此都能獨立運行。然而全部同源窗口會共享一個event loop以同步通訊。event loop會一直運行,來執行進入隊列的宏任務。一個event loop有多種的宏任務源(譯者注:event等等),這些宏任務源保證了在本任務源內的順序。可是瀏覽器每次都會選擇一個源中的一個宏任務去執行。這保證了瀏覽器給與一些宏任務(如用戶輸入)以更高的優先級。好的,跟着我繼續……api
瀏覽器爲了可以使得JS內部task與DOM任務可以有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行從新渲染 (task->渲染->task->...)
鼠標點擊會觸發一個事件回調,須要執行一個宏任務,而後解析HTMl。還有下面這個例子,setTimeoutpromise
setTimeout的做用是等待給定的時間後爲它的回調產生一個新的宏任務。這就是爲何打印‘setTimeout’在‘script end’以後。由於打印‘script end’是第一個宏任務裏面的事情,而‘setTimeout’是另外一個獨立的任務裏面打印的。瀏覽器
微任務一般來講就是須要在當前 task 執行結束後當即執行的任務,好比對一系列動做作出反饋,或或者是須要異步的執行任務而又不須要分配一個新的 task,這樣即可以減少一點性能的開銷。只要執行棧中沒有其餘的js代碼正在執行且每一個宏任務執行完,微任務隊列會當即執行。若是在微任務執行期間微任務隊列加入了新的微任務,會將新的微任務加入隊列尾部,以後也會被執行。微任務包括了mutation observe的回調還有接下來的例子promise的回調。app
一旦一個pormise有告終果,或者早已有告終果(有告終果是指這個promise到了fulfilled或rejected狀態),他就會爲它的回調產生一個微任務,這就保證了回調異步的執行即便這個promise早已有告終果。因此對一個已經有告終果的promise調用.then(yey, nay)會當即產生一個微任務。這就是爲何‘promise1’,'promise2'會打印在‘script end’以後,由於全部微任務執行的時候,當前執行棧的代碼必須已經執行完畢。‘promise1’,'promise2'會打印在‘setTimeout’以前是由於全部微任務總會在下一個宏任務以前所有執行完畢。less
逐步執行demo:譯者注,這裏做者實現了一個相似於debug,逐步執行的demo,其中還加入了執行棧的動畫還有講解,建議你們去原文觀看
原文
是的,我弄了一個逐步的圖標。你怎麼度過你的週六?和你的朋友出去享受陽光?emmmm,若是對我驚豔的ui交互設計看不懂,點擊左右箭頭試試吧。
有些瀏覽會會打印出:
script start, script end, setTimeout, promise1, promise2。
他們會在setTimeout以後執行promise的回調,就好像這些瀏覽器會把promise的回調視做一個新的宏任務而不是微任務。
其實無可厚非,由於promises 來自於ECMAScript 的標準而不是HTML標準。
ECMAScript 有個關於jobs的概念和微任務挺相似的,可是否明確具備關聯關係卻還沒有定論(相關討論)。然而,廣泛的觀點是promise應該屬於微任務。
若是說把 promise 當作一個新的 task 來執行的話,這將會形成一些性能上的問題,由於 promise 的回調函數可能會被延遲執行,由於在每個 task 執行結束後瀏覽器可能會進行一些渲染工做。因爲做爲一個 task 將會和其餘任務來源(task source)相互影響,這也會形成一些不肯定性,同時這也將打破一些與其餘 API 的交互,這樣一來便會形成一系列的問題。
這裏有一個關於讓Edge把promise加入微任務的提議,其實WebKit 早已悄悄正確實現。因此我猜Safari最終會修復,Firefox 43好像已修復。
實際測試是一種方法,觀察日誌打印順序與promise和setTimeout的關係,可是首先瀏覽器對這二者的實現要正確。
還有一個穩妥方法就是看文檔,好比setTimeout是宏任務,mutation是微任務。
正如上文提到的,ECMAScript 中把微任務叫作jobs,EnqueueJob
是微任務。
接下來,讓咱們看一些複雜的例子吧
寫這篇文章前我就犯了這個錯。來看代碼
<div class="outer"> <div class="inner"></div> </div>
在看接下來的js代碼,若是我點擊div.inner會打印什麼?
// Let's get hold of those elements var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); // Let's listen for attribute changes on the // outer element //監聽element屬性變化 new MutationObserver(function() { console.log('mutate'); }).observe(outer, { attributes: true }); // Here's a click listener… function onClick() { console.log('click'); setTimeout(function() { console.log('timeout'); }, 0); Promise.resolve().then(function() { console.log('promise'); }); outer.setAttribute('data-random', Math.random()); } // …which we'll attach to both elements inner.addEventListener('click', onClick); outer.addEventListener('click', onClick);
偷看答案前先試一試啊,tips:日誌可能出現屢次哦。
結果以下:
click
promise
mutate
click
promise
mutate
timeout
timeout
你猜對了嗎。你可能猜對了,可是許多瀏覽器卻不這樣以爲。
譯者注:譯者本機測試
Chrome( 64.0.3282.167(正式版本) (64 位))相同,
Edge(Edge 38.14393.2068.0)不一樣(與Chrome順序相同)
Firefox 32位 59.0.2
分發click event是一個宏任務,Mutation observer和promise都會進入微任務隊列,setTimeout回調是一個宏任務,因此來看demo
做者演示demo,建議原文觀看demo
因此chrome是對的,我以前也不知道只要執行棧中沒有js代碼在執行,微任務會在回調後當即執行,我以前認爲它只會在宏任務結束後執行(Although we are mid-task,microtasks are processed after callbacks if the stack is empty).這個規則來自於HTML標準中關於回調調用的部分
If the stack of script settings objects is now empty, perform a microtask checkpoint
— HTML: Cleaning up after a callback step 3
若是js執行棧空了,當即執行microtask checkpoint
—— HTML: Cleaning up after a callback
microtask checkpoint 會檢查整個微任務隊列,除非正在執行這個檢查動做。ECMAScript 標準中說到
Execution of a Job can be initiated only when there is no running execution context and the execution context stack is empty…
— ECMAScript: Jobs and Job Queues
HTML環境下,必須執行。
Firefox和Safari在click監聽器回調之間正確執行了mutation 回調的微任務,但promise打印結果卻出如今了錯誤的位置。
無可厚非的是jobs和微任務的關係太含糊不清,不過我仍認爲應該在click監聽器回調之間執行。
Edge咱們早就知道會把promise回調放進錯誤的隊列,但他也也沒在click監聽器回調之間執行微任務隊列,而是在全部監聽器回調後執行,這打印click以後只打印了一次muteta,爲此我給它提了個bug。
用剛纔的代碼,若是咱們這樣執行會發生什麼。
inner.click();
這依舊會開始分發事件,但此次是使用腳本而不是交互點擊。
click
click
promise
mutate
promise
timeout
timeout
我發誓我從chrome獲得的答案一直不同- -。我已經更新了這個表許許屢次了。我以爲我是錯誤地測試了Canary。假如你在 Chrome 中獲得了不一樣的結果,請在評論中告訴我是哪一個版本。
來看demo發生了什麼,原做者的演示demo
因此正確的順序是click, click, promise, mutate, promise, timeout, timeout,看來chrome是對的。
在每一個監聽器回調調用以後
If the stack of script settings objects is now empty, perform a microtask checkpoint
— HTML: Cleaning up after a callback step 3
以前的例子,微任務會在監聽器回調之間執行。但這裏的例子,click()會致使事件同步分發,因此在監聽器回調之間Js執行棧不爲空,而上述的這個規則保證了微任務不會打斷正在執行的js.這意味着咱們不能在監聽器回調之間執行微任務,微任務會在監聽器以後執行。
譯者注:對IndexedDB 理解不深刻,這段就不翻譯了- -
Yeah, it'll bite you in obscure places (ouch). I encountered this while trying to create a simple wrapper library for IndexedDB that uses promises rather than weird IDBRequest objects. It almost makes IDB fun to use.
When IDB fires a success event, the related transaction object becomes inactive after dispatching (step 4). If I create a promise that resolves when this event fires, the callbacks should run before step 4 while the transaction is still active, but that doesn't happen in browsers other than Chrome, rendering the library kinda useless.
You can actually work around this problem in Firefox, because promise polyfills such as es6-promise use mutation observers for callbacks, which correctly use microtasks. Safari seems to suffer from race conditions with that fix, but that could just be their broken implementation of IDB. Unfortunately, things consistently fail in IE/Edge, as mutation events aren't handled after callbacks.
Hopefully we'll start to see some interoperability here soon.
總結一下:
全部微任務也按順序執行,且在如下場景會當即執行全部微任務
但願你已經熟悉了eventloop.