目錄
- 為什么分析asap
- asap概述
- asap原始碼決議—Node版
- 參考
1.為什么分析asap
在之前的文章 async和await是如何實作異步編程? 中的 “淺談Promise如何實作異步執行” 小節,提到了 Promise 異步執行是通過 asap 這個庫來實作的,所以為了進一步深入 Promise 異步執行的原理,深入分析一下 asap 是有必要的,
補充說明:這里提及的Promise并不是Node和瀏覽器的原生實作,是一個第三方庫實作,僅以此為參考,
2.asap概述
asap 是 as soon as possible 的簡稱,在 Node 和瀏覽器環境下,能將回呼函式以高優先級任務來執行(下一個事件回圈之前),即把任務放在微任務佇列中執行,
宏任務(macro-task)和微任務(micro-task)表示異步任務的兩種分類,在掛起任務時,JS 引擎會將所有任務按照類別分到這兩個佇列中,首先在 macrotask 的佇列(這個佇列也被叫做 task queue)中取出第一個任務,執行完畢后取出 microtask 佇列中的所有任務順序執行;之后再取 macrotask 任務,周而復始,直至兩個佇列的任務都取完,
用法:
asap(function () {
// ...
});
3.asap原始碼決議—Node版
asap 原始碼庫中包含了支持Node和瀏覽器的兩個版本,這里主要進行分析Node版,
主要包含兩個原始碼檔案:
- asap.js
- raw.js
這兩個檔案分別匯出了 asap 和 rawAsap 這兩個方法,而 asap 可以看作是對 rawAsap 的進一步封裝,通過快取的 domain(可以捕捉處理 try catch 無法捕捉的例外,針對異步代碼的例外處理)和 try/finally 實作了即使某個任務拋出例外也可以恢復任務堆疊的繼續執行,另外也做了一點快取優化(具體見原始碼),
因此這里主要分析 raw.js 里面的代碼即可:
1.首先是對外匯出的 rawAsap 方法
var queue = [];
var flushing = false;
function rawAsap(task) {
if (!queue.length) {
requestFlush();
flushing = true;
}
queue[queue.length] = task;
}
原始碼決議:如果任務堆疊 queue 為空,則觸發 requestFlush 方法,并將 flushing 標志為 true,并且始侄訓將要執行的 task 添加到任務堆疊 queue 的末尾,這里需要注意的是由于 requestFlush 中是異步去觸發任務堆疊的執行的,所以即使queue[queue.length] = task在 requestFlush 呼叫之后執行,也能保證在任務堆疊 queue 真正執行前,任務 task 已經被添加到了任務堆疊 queue 的末尾,(如果任務堆疊 queue 不為空,說明 requestFlush 已經觸發了,此時任務堆疊正在被回圈依次執行,執行完畢會清空任務堆疊)
2.其次是異步觸發 flush 方法執行的 requestFlush 方法
var domain;
var hasSetImmediate = typeof setImmediate === "function";
// 設定為 rawAsap 的屬性,方便在任務執行例外時再次觸發 requestFlush
rawAsap.requestFlush = requestFlush;
function requestFlush() {
// 確保 flushing 未系結到任何域
var parentDomain = process.domain;
if (parentDomain) {
if (!domain) {
// 惰性加載執行 domain 模塊
domain = require("domain");
}
domain.active = process.domain = null;
}
if (flushing && hasSetImmediate) {
setImmediate(flush);
} else {
process.nextTick(flush);
}
if (parentDomain) {
domain.active = process.domain = parentDomain;
}
}
原始碼決議:核心代碼其實就一句:setImmediate(flush),通過 setImmediate 異步執行 flush 方法,而判斷 parentDomain 以及設定和恢復 domain 都只是為了當前的 flush 方法不系結任何域執行,而這里還有一個 hasSetImmediate 判斷,是為了做兼容降級處理,如果不存在 setImmediate 方法,則使用 process.nextTick 方法觸發異步執行,但使用 process.nextTick 方法有一個缺陷,就是它不能夠處理遞回,
3.最后是執行任務堆疊的 flush 方法
// 下一個任務在任務佇列中執行的位置
var index = 0;
var capacity = 1024;
function flush() {
while (index < queue.length) {
var currentIndex = index;
// 在呼叫任務之前先設定下一個任務的索引,可以確保再次觸發 flush 方法時,跳過例外任務
index = index + 1;
queue[currentIndex].call();
// 防止記憶體泄露
if (index > capacity) {
for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) {
queue[scan] = queue[scan + index];
}
queue.length -= index;
index = 0;
}
}
queue.length = 0;
index = 0;
flushing = false;
}
原始碼決議:通過 while 回圈依次去執行任務堆疊 queue 中的每一個任務,這里需要注意一點,index + 1 表示下一個要執行的任務下標,而其放在 queue[currentIndex].call() 之前,是為了保證當當前任務執行發生例外了,再次觸發 requestFlush 方法時,能夠跳過發生例外的任務,從下一個任務開始執行,而判斷 if (index > capacity) 是為了防止記憶體泄露,當任務堆疊 queue 的長度超過了指定的閾值 capacity 時,對任務堆疊 queue 中的任務進行移動,將所有剩余的未執行的任務置前,并重置任務堆疊 queue 的長度,當所有任務執行完畢后,重置任務堆疊以及相應狀態,
4.總結
rawAsap 方法是通過 setImmediate 或 process.nextTick 來實作異步執行的任務堆疊,而 asap 方法是對 rawAsap 方法的進一步封裝,通過快取的 domain 和 try/finally 實作了即使某個任務拋出例外也可以恢復任務堆疊的繼續執行(再次呼叫rawAsap.requestFlush),
4.參考
【翻譯】Promises/A+規范
asap - High-priority task queue for Node.js and browsers
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/122061.html
標籤:JavaScript
下一篇:React 中的生命周期函式
