JavaScript在瀏覽器裏的執行流程跟在Node.js中同樣,是基於事件循環的。javascript
事件循環:一個在JavaScript引擎等待任務、執行任務和休眠等待更多任務這幾個狀態之間的無窮無盡的循環。java
執行引擎通用的算法:算法
一個任務到來時引擎可能正處於運行狀態,那麼這個任務就被入隊。多個任務組成了一個隊列,命名爲「宏任務隊列」(v8 術語),引擎按照先進先出來處理它們,而後等待更多的任務(即休眠,幾乎不消耗 CPU 資源)。promise
宏任務舉例:瀏覽器
當引擎忙於執行一段 script 時,還可能有用戶移動鼠標產生了 mousemove 事件,setTimeout 或許也恰好到期等這些事件,這些任務組成一個隊列:markdown
當引擎處理任務時不會執行渲染,對於 DOM 的修改只有當任務執行完成纔會被繪製。 若是一個任務執行時間過長,瀏覽器沒法處理其餘任務,在必定時間後就會在整個頁面拋出一個如「頁面未響應」的警示建議終止這個任務。這樣的場景常常發生在不少複雜計算或者程序錯誤執行到死循環裏。網絡
使用 0 延時的 setTimeout(f)來計劃一個新的宏任。 它被用來拆分一個計算耗費型任務爲小片斷,使瀏覽器能夠對用戶行爲做出反饋和展現計算的進度。 也被用在事件處理函數中來定時執行一個行爲,在當前事件被徹底處理(冒泡結束)以後。異步
微任務僅僅由咱們的代碼產生。它們一般由 promises 生成:對於 .then/catch/finally 的處理函數變成了一個微任務。微任務一般"隱藏在" await 下,由於它也是另外一種處理 promise 的形式。函數
一個宏任務結束後,先執行全部微任務隊列中的任務,而後再去執行其餘宏任務或渲染。微任務優先級高保證了微任務中的程序運行環境基本一致(沒有鼠標位置改變,沒有新的網絡返回數據,等等)。 有一個特殊的函數 queueMicrotask(func),能夠將 func 加入到微任務隊列來執行。若是咱們想要異步執行(在當前代碼以後)一個函數,可是要在修改被渲染或者新的事件被處理以前,咱們能夠用 queueMicrotask 來定時執行。spa
setTimeout(() => alert("timeout")); Promise.resolve() .then(() => alert("promise")); alert("code"); // code -> promise -> timeout 複製代碼
Promise 的處理程序(handlers).then、.catch 和 .finally 都是異步的。 異步任務須要適當的管理。爲此,JavaScript 標準規定了一個內部隊列 PromiseJobs —— 「微任務隊列」(Microtasks queue)(v8 術語)。 這個隊列先進先出,只有引擎中沒有其餘任務運行時纔會啓動任務隊列的執行。 當一個 promise 準備就緒時,它的 .then/catch/finally 處理程序就被放入隊列中。等到當前代碼執行完而且以前排好隊的處理程序都完成時,JavaScript引擎會從隊列中獲取這些任務並執行。 即使一個 promise 當即被 resolve,.then、.catch 和 .finally 以後的代碼也會先執行。 若是要確保一段代碼在 .then/catch/finally 以後被執行,最好將它添加到 .then 的鏈式調用中。
let promise = Promise.resolve(); promise.then(() => alert("promise done")); alert("code finished"); // 該警告框會首先彈出 複製代碼
指在 microtask 隊列結束時未處理的 promise 錯誤。 microtask隊列完成時,引擎會檢查promise,若是其中任何一個出現rejected狀態,就會觸發unhandledrejection事件。 但若是在setTimeout裏進行catch,unhandledrejection會先觸發,而後catch才執行,因此catch沒有發揮做用。
let promise = Promise.reject(new Error("Promise Failed!")); setTimeout(() => promise.catch(err => alert('caught'))); window.addEventListener('unhandledrejection', event => alert(event.reason)); // Promise Failed! -> caught 複製代碼