[英]JS async / await tasks queue
在我的 JS 應用程序中,我正在使用 async / await 功能。 我想執行多個 API 調用,並希望它們一個接一個地被解雇。 換句話說,我想替換這個簡單的方法:
const addTask = async (url, options) => {
return await fetch(url, options)
}
有更復雜的東西..比如:
let tasksQueue = []
const addTask = async (url, options) => {
tasksQueue.push({url, options})
...// perform fetch in queue
return await ...
}
處理異步返回的最佳方法是什么?
您可以使用Queue數據結構作為基礎並在子類中添加特殊行為。 一個Queue
有兩個方法enqueue()
(將新項目添加到末尾)和dequeue()
(刪除第一個項目)的眾所周知的接口。 在您的情況下, dequeue()
等待異步任務。
特殊行為:
- 每次新任務(例如
fetch('url')
)入隊時,都會調用this.dequeue()
。dequeue()
的作用:
- 如果隊列為空 ➜
return false
(脫離遞歸)- 如果隊列忙 ➜
return false
(上一個任務未完成)- else ➜ 從隊列中刪除第一個任務並運行它
- 任務“完成”(成功或有錯誤)➜遞歸調用
dequeue()
(2.),直到隊列為空..
class Queue { constructor() { this._items = []; } enqueue(item) { this._items.push(item); } dequeue() { return this._items.shift(); } get size() { return this._items.length; } } class AutoQueue extends Queue { constructor() { super(); this._pendingPromise = false; } enqueue(action) { return new Promise((resolve, reject) => { super.enqueue({ action, resolve, reject }); this.dequeue(); }); } async dequeue() { if (this._pendingPromise) return false; let item = super.dequeue(); if (!item) return false; try { this._pendingPromise = true; let payload = await item.action(this); this._pendingPromise = false; item.resolve(payload); } catch (e) { this._pendingPromise = false; item.reject(e); } finally { this.dequeue(); } return true; } } // Helper function for 'fake' tasks // Returned Promise is wrapped! (tasks should not run right after initialization) let _ = ({ ms, ...foo } = {}) => () => new Promise(resolve => setTimeout(resolve, ms, foo)); // ... create some fake tasks let p1 = _({ ms: 50, url: '❪𝟭❫', data: { w: 1 } }); let p2 = _({ ms: 20, url: '❪𝟮❫', data: { x: 2 } }); let p3 = _({ ms: 70, url: '❪𝟯❫', data: { y: 3 } }); let p4 = _({ ms: 30, url: '❪𝟰❫', data: { z: 4 } }); const aQueue = new AutoQueue(); const start = performance.now(); aQueue.enqueue(p1).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // = 50 aQueue.enqueue(p2).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 50 + 20 = 70 aQueue.enqueue(p3).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 70 + 70 = 140 aQueue.enqueue(p4).then(({ url, data }) => console.log('%s DONE %fms', url, performance.now() - start)); // 140 + 30 = 170
完整代碼演示: https ://codesandbox.io/s/async-queue-ghpqm?file=/src/index.js 您可以在控制台和/或開發工具的“性能”選項卡中玩耍並觀察結果。 這個答案的其余部分是基於它的。
enqueue()
返回一個新的Promise
,它將在稍后的某個時間解決(或拒絕)。 這個Promise
可以用來處理你的async
任務 Fn 的響應。
enqueue()
實際上是push()
一個Object
到隊列中,它保存了任務 Fn 和返回的 Promise 的控制方法。
由於解包返回Promise
insta。 開始運行時,每次我們將新任務加入隊列時都會調用this.dequeue()
。
將一些performance.measure()添加到我們的task
中,我們可以很好地可視化我們的隊列:
tasks
有一個“❚❚ 等待..”期間(第 3 行)(如果隊列為空,則可能< 1ms
)日志輸出( console.table() ):
說明:第一個
task
是隊列初始化后2.58ms
的enqueue()
d。 由於我們的隊列是空的,所以沒有❚❚ waiting
(0.04ms
➜ ~40μm
)。 任務運行時間13.88ms
➜ 出隊
Queue
只是原生Array
Fn的包裝器! 您當然可以在一個類中實現這一點。 我只是想表明,你可以從已知的數據結構中構建你想要的東西。 不使用Array
有一些很好的理由:
Queue
數據結構由兩個公共方法的接口定義。 使用Array
可能會誘使其他人在其上使用本機Array
方法,例如.reverse()
,.. 這會破壞定義。enqueue()
和dequeue()
比push()
和shift()
更具可讀性Queue
類,您可以從它擴展(可重用代碼)class Queue
中的項Array
替換為其他數據結構:“ 雙鏈表”,它將Array.shift()
的代碼復雜度從 O(n) [線性] 降低到 O(1) [常量]。 (➜ 比原生數組 Fn 更好的時間復雜度!)(➜ 最終演示)這個AutoQueue
類不限於async
函數。 它處理任何事情,可以像await item[MyTask](this)
一樣調用:
let task = queue => {..}
➜同步函數let task = async queue => {..}
➜異步函數let task = queue => new Promise(resolve => setTimeout(resolve, 100)
➜ new Promise()
注意:我們已經用
await
調用了我們的任務,其中await
將任務的響應包裝到Promise
中。 Nr 2.(異步函數),總是自己返回一個Promise
,而await
調用只是將一個Promise
包裝到另一個Promise
中,效率稍低。 Nr 3. 很好。 返回的 Promise不會被await
包裹
這是異步函數的執行方式:( 來源)
p
。 該 Promise 是在開始執行 async 函數時創建的。p
。以下代碼演示了它是如何工作的:
async function asyncFunc() {
console.log('asyncFunc()'); // (A)
return 'abc';
}
asyncFunc().
then(x => console.log(`Resolved: ${x}`)); // (B)
console.log('main'); // (C)
// Output:
// asyncFunc()
// main
// Resolved: abc
您可以依賴以下順序:
由於AutoQueue
僅限於處理一個接一個的任務,因此它可能會成為我們應用程序的瓶頸。 限制因素是:
enqueue()
d 任務的頻率。dequeue()
中的阻塞時間,直到任務完成這是我們的責任! 我們可以隨時獲取queue
的當前大小: size = queue.size
。 您的外部腳本需要一個穩定增長的隊列的“故障轉移”案例(檢查“堆疊wait
時間”部分)。
您想避免像這樣的“隊列溢出”,平均/平均等待時間會隨着時間的waitTime
而增加。
+-------+----------------+----------------+----------------+----------------+
| tasks | enqueueMin(ms) | enqueueMax(ms) | runtimeMin(ms) | runtimeMax(ms) |
| 20 | 0 | 200 | 10 | 30 |
+-------+----------------+----------------+----------------+----------------+
20/20
等待195ms
,直到 exec 開始這個比較難對付。 (等待fetch()
無法改進,我們需要等到 HTTP 請求完成)。
也許您的fetch()
任務依賴於彼此的響應,並且長時間運行會阻塞其他任務。
但是我們可以做一些事情:
也許我們可以緩存響應 ➜ 減少下一個入隊的運行時間。
也許我們從 CDN fetch()
並有一個我們可以使用的替代 URI。 在這種情況下,我們可以從我們的task
中返回一個new Promise
,它將在下一個task
enqueue()
d 之前運行。 (參見“錯誤處理”):
queue.enqueue(queue => Promise.race(fetch('url1'), fetch('url2')));
也許您有某種“長輪詢”或周期性 ajax task
,每 x 秒運行一次,無法緩存。 即使您不能減少運行時間本身,您也可以記錄運行時間,這會給您一個近似值。 估計下一次運行。 也許可以將長時間運行的任務交換到其他隊列實例。
AutoQueue
什么是“高效” Queue
? - 你的第一個想法可能是這樣的:
最高效的
Queue
在最短的時間內處理最多的tasks
?
既然我們不能提高我們的task
運行時間,我們可以減少等待時間嗎? 該示例是一個queue
,任務之間的等待時間為零( ~0ms
)。
提示:為了比較我們的下一個示例,我們需要一些不會改變的基本統計數據:
+-------+----------------+----------------+------------------+------------------+
| count | random fake runtime for tasks | random enqueue() offset for tasks |
+-------+----------------+----------------+------------------+------------------+
| tasks | runtimeMin(ms) | runtimeMax(ms) | msEnqueueMin(ms) | msEnqueueMax(ms) |
| 200 | 10 | 30 | 0 | 4000 |
+-------+----------------+----------------+------------------+------------------+
Avg. task runtime: ⇒ (10ms + 30ms) / 2 = 20ms
Total time: ⇒ 20ms * 200 = 4000ms ≙ 4s
➜ We expect our queue to be resolved after ~4s
➜ For consistent enqueue() frequency we set msEnqueueMax to 4000
AutoQueue
在~4.12s
4.12 秒后完成了最后一次dequeue()
(^^ 參見工具提示)。4s
~120ms
:
提示:在每個任務
~0.3ms
之后有一個小的“日志”塊”,我在其中構建/推送一個帶有日志標記的Object
到最后的console.table()
日志的全局“數組”。這解釋了200 * 0.3ms = 60ms
.. 缺少的60ms
未被跟蹤(您會看到任務之間的小間隙)->0.3ms
/task 用於我們的測試循環,並且可能由於打開的 Dev-Tools 造成了一些延遲,..
我們稍后會回到這些時間。
我們queue
的初始化代碼:
const queue = new AutoQueue();
// .. get 200 random Int numbers for our task "fake" runtimes [10-30]ms
let runtimes = Array.from({ length: 200 }, () => rndInt(10, 30));
let i = 0;
let enqueue = queue => {
if (i >= 200) {
return queue; // break out condition
}
i++;
queue
.enqueue(
newTask({ // generate a "fake" task with of a rand. runtime
ms: runtimes[i - 1],
url: _(i)
})
)
.then(payload => {
enqueue(queue);
});
};
enqueue(queue); // start recurion
在前一個任務完成之后,我們遞歸地enqueue()
我們的下一個任務。 您可能已經注意到與典型Promise.then()
鏈的類比,對吧?
提示:如果我們已經知道按順序運行的
tasks
的順序和總數,我們就不需要Queue
。 我們可以使用Promise
鏈並獲得相同的結果。
有時我們在腳本開始時並不知道所有后續步驟..
..您可能需要更大的靈活性,我們要運行的下一個任務取決於上一個task
的響應。 - 也許您的應用程序依賴於 REST API(多個端點),並且您被限制為最多 X 個同時 API 請求。 我們不能使用來自您應用程序的所有請求向 API 發送垃圾郵件。 你甚至不知道下一個請求何時獲得enqueue()
d(例如 API 請求由click()
事件觸發?..
好的,對於下一個示例,我稍微更改了初始化代碼:
我們現在在 [0-4000ms] 時間內隨機排列 200 個任務。 - 公平地說,我們將范圍減少了 30 30ms
(最大任務運行時間)到 [0-3970 毫秒]。 現在我們隨機填充的隊列有機會保持在4000ms
限制內。
我們可以得到什么或 Dev-Tools 性能登錄:
enqueue()
會導致大量“等待”任務。
有道理,因為我們在第一個
~4000ms
內將所有任務排入隊列,它們必須以某種方式重疊。 檢查表輸出,我們可以驗證:在任務170/200
入隊時,最大queue.size
為22
。
由於隨機
enqueue()
它不可能為我們的第一個任務獲得0ms
偏移量。 每個任務~20ms
的運行時間會隨着時間的推移導致堆疊效應。
>400ms
毫秒。
queue.size
(列:sizeOnAdd
)和wait ms
之間可能存在關系(請參閱下一節)。
AwaitQueue
在其初始化后完成了最后一個dequeue()
~4.37s
(檢查“性能”選項卡中的工具提示)。 20,786ms / task
的平均運行時間(預期:20 20ms
)使我們的總運行時間為4157.13ms
(預期: 4000ms
毫秒≙ 4s
)。
我們仍然有我們的“日志”塊和 exec。 我們的測試腳本的時間它自己
~120ms
。~37ms
? 在開始時總結所有空閑的“差距”解釋了缺失的~37ms
最高效的
Queue
在最短的時間內處理最多的tasks
?
假設:除了隨機偏移,前面例子中的tasks
得到enqueue()
d,兩個隊列在同一時間段內處理了相同數量的task
(等於 avg.runtime )。 入隊task
的等待時間和queue.size
都不會影響總運行時間。 兩者效率一樣嗎?
由於Queue
本質上會縮小我們的編碼可能性,因此如果我們談論高效代碼(每次任務數),最好不要使用Queue
。
隊列幫助我們將異步環境中的任務整理成同步模式。 這正是我們想要的。 ➜“連續運行未知的任務序列”。
如果你發現自己問這樣的問題:“如果一個新task
被排入一個已經填滿的隊列,那么我們等待結果的時間會因其他任務的運行時間而增加。效率較低!”。 那么你做錯了:
wait
時間我們看到一個任務在運行之前的峰值wait
時間為461.05ms
。 如果我們能夠在決定將其入隊之前預測任務的wait
時間,那不是很好嗎?
首先,我們分析AutoQueue
類在較長時間內的行為。 (重新發布屏幕)
我們可以從console.table()
輸出構建圖表:
除了task
的wait
時間,我們可以看到隨機的 [10-30ms] runtime
和 3 條曲線,代表當前queue.size
,記錄在task
的時間..
enqueued()
dequeue()
)dequeue()
之前)再運行 2 次進行比較(類似趨勢):
如果我們能找到這些記錄的圖表線之間的關系,它可能會幫助我們了解queue
隨時間的行為(➜ 不斷填充新任務)。
Exkurs:什么是關系? 我們正在尋找一個方程,將
wait ms
曲線投影到3 個queue.size
記錄之一。 這將證明兩者之間的直接依賴關系。
在上次運行中,我們更改了啟動參數:
任務數: 200
➜ 1000
(5x)
msEnqueueMax: 4000ms
➜ 20000ms
(5x)
+-------+----------------+----------------+------------------+------------------+ | count | random fake runtime for tasks | random enqueue() offset for tasks | +-------+----------------+----------------+------------------+------------------+ | tasks | runtimeMin(ms) | runtimeMax(ms) | msEnqueueMin(ms) | msEnqueueMax(ms) | | 1000 | 10 | 30 | 0 | 20000 | +-------+----------------+----------------+------------------+------------------+ Avg. task runtime: ⇒ (10ms + 30ms) / 2 = 20ms (like before) Total time: ⇒ 20ms * 1000 = 20000ms ≙ 20s ➜ We expect our queue to be resolved after ~20s ➜ For consistent enqueue() frequency we set msEnqueueMax to 20000
(互動圖表: https ://datawrapper.dwcdn.net/p4ZYx/2/)
我們看到了同樣的趨勢。 wait ms
隨着時間的推移而增加(沒有什么新東西)。 由於底部的 3 個queue.size
線被繪制到同一個圖表中(Y 軸具有ms
刻度),因此它們幾乎不可見。 快速切換到對數刻度以進行更好的比較:
(互動圖表: https ://datawrapper.dwcdn.net/lZngg/1/)
queue.size [on start]
和queue.size [on end]
的兩條虛線幾乎相互重疊,一旦我們的隊列最后變空,就會下降到“0”。
queue.size [on add]
看起來與wait ms
行非常相似。 這就是我們需要的。
{queue.size [on add]} * X = {wait ms}
⇔ X = {wait ms} / {queue.size [on add]}
僅此一項在運行時對我們沒有幫助,因為wait ms
對於新的排隊任務(尚未運行)是未知的。 所以我們還有 2 個未知變量: X
和wait ms
。 我們需要另一個可以幫助我們的關系。
首先,我們將我們的新配給{wait ms} / {queue.size [on add]}
打印到圖表中(淺綠色),以及它的平均值/平均值(淺綠色水平虛線)。 這非常接近20ms
(平均run ms
數),對吧?
切換回linear
Y 軸並將其“最大比例”設置為80ms
以獲得更好的視圖。 (提示: wait ms
現在超出了視口)
(互動圖表: https ://datawrapper.dwcdn.net/Tknnr/4/)
回到我們任務的隨機運行時間(點雲)。 我們仍然有20.72ms
的“總平均值”(深綠色水平虛線)。 我們還可以在運行時計算之前任務的平均值(例如,任務 370 入隊 ➜ 任務 [1,..,269] 的當前平均運行時間 = 平均運行時間)。 但我們甚至可以更精確:
我們排隊的任務越多,它們對總“平均運行時間”的影響就越小。 因此,讓我們計算最后例如 50 個tasks
的“平均運行時間”。 這導致每個任務對“平均運行時間”產生 1/50 的一致影響。 ➜ 峰值運行時間變直,趨勢(上升/下降)被考慮在內。 (我們的 1. 方程中的淺綠色旁邊的深綠色水平路徑曲線)。
我們現在可以做的事情:
我們可以從第一個方程中消除X
(淺綠色)。 ➜ X
可以表示為“先前n
個任務的平均運行時間,例如 50 個任務(深綠色)。我們的新方程僅取決於在運行時已知的變量,就在入隊點:
// mean runtime from prev. n tasks: X = {[taskRun[-50], .. , taskRun[-2], taskRun[-1] ] / n } ms // .. replace X in 1st equation: ⇒ {wait ms} = {queue.size [on add]} * {[runtime[-50], .. , runtime[-2], runtime[-1] ] / n } ms
我們可以在我們的圖表上繪制一條新的圖表曲線,並檢查它與記錄的wait ms
(橙色)相比有多接近
(互動圖表: https ://datawrapper.dwcdn.net/LFp1d/2/)
考慮到我們的任務的運行時間可以通過某種方式確定這一事實,我們可以在任務入隊之前預測它的wait
時間。 因此,它在您將相同類型/功能的任務排入隊列的情況下效果最佳: 用例:一個AutoQueue
實例充滿了 UI 組件的渲染任務。 渲染時間可能不會對聊天有太大影響(與fetch()
相比)。 也許您在地圖上渲染 1000 個位置標記。 每個標記都是一個帶有render()
Fn 的類的實例。
Queues
用於各種任務。 ➜ 為不同類型的邏輯實現專用的Queue
類變體(不要在一個類中混合不同的邏輯)AutoQueue
實例(現在或將來)的task
,它們可能被所有其他的阻塞。AutoQueue
不會改善運行時間,充其量不會降低。Task
類型使用不同的AutoQueue
實例。AutoQueue
的大小,特別是 ..
enqueue()
)task
運行時tasks
中的錯誤只會reject
他們在入隊時返回的承諾( promise = queue.enqueue(..)
)並且不會停止出隊過程。 你可以處理錯誤..
return new Promise()
queue.enqueue(..).catch(e => {..})
AutoQueue
類中的錯誤處理程序Queue
的實現,您可能會看到queue.size
。 一個包含 1000 個任務的Array
不如我在最終代碼中使用的分散數據結構(如“雙鏈表”)有效。enqueue()
其他人的tasks
) - 但是,調試AutoQueue
並不有趣,其中tasks
是在async
環境中由其他人動態enqueue()
e..Queue
可能會解決問題(在某個抽象級別上)。 但是,在大多數情況下,它會縮小現有的靈活性。 它為我們的代碼添加了一個額外的“控制層”(在大多數情況下,這正是我們想要的),同時,我們簽署了一份合同來接受Queue
的嚴格規則。 即使它解決了一個問題,它也可能不是最好的解決方案。 在enqueue()
上停止“Auto dequeue()
”:由於我們的AutoQueue
類是通用的並且不限於長時間運行的HTTP requests(),因此您可以enqueue()
任何必須按順序運行的函數,甚至3min
運行的函數,例如“存儲模塊的更新”,.. 您不能保證,當您在一個循環中enqueue()
100 個任務時,上一個添加的任務尚未dequeued()
。
您可能希望阻止
enqueue()
調用dequeue()
直到添加所有位置。
enqueue(action, autoDequeue = true) { // new return new Promise((resolve, reject) => { super.enqueue({ action, resolve, reject }); if (autoDequeue) this.dequeue(); // new }); }
..然后在某個時候手動調用queue.dequeue()
。
控制方式: stop
/ pause
/ start
可以添加更多的控制方式。 也許您的應用程序有多個模塊,它們都試圖在頁面加載時fetch()
那里的資源。 AutoQueue()
像Controller
一樣工作。 您可以監控有多少任務正在“等待..”並添加更多控件:
class AutoQueue extends Queue { constructor() { this._stop = false; // new this._pause = false; // new } enqueue(action) { .. } async dequeue() { if (this._pendingPromise) return false; if (this._pause ) return false; // new if (this._stop) { // new this._queue = []; this._stop = false; return false; } let item = super.dequeue(); .. } stop() { // new this._stop = true; } pause() { // new this._pause = true; } start() { // new this._stop = false; this._pause = false; return await this.dequeue(); } }
轉發響應:您可能希望在下一個task
中處理任務的“響應/值”。 不能保證我們的上一個。 任務尚未完成,此時我們將第二個任務排入隊列。 因此,最好存儲上一個的響應。 類內的任務並將其轉發給下一個: this._payload = await item.action(this._payload)
task
Fn 中拋出的錯誤拒絕enqueue()
返回的承諾,並且不會停止出隊過程。 您可能希望在下一個task
開始運行之前處理錯誤:
queue.enqueue(queue => myTask() ).catch({ .. }); // async error handling
queue.enqueue(queue =>
myTask()
.then(payload=> otherTask(payload)) // .. inner task
.catch(() => { .. }) // sync error handling
);
由於我們的Queue
是dump ,並且只是await
我們的任務被解決( item.action(this)
),所以沒有人阻止您從當前正在運行的task
Fn返回一個new Promise()
。 - 它將在下一個任務出隊之前解決。
您可以在任務 Fn 中throw new Error()
並在“外部”/運行后處理它們: queue.enqueue(..).catch()
。 您可以輕松地在dequeue()
方法中添加自定義錯誤處理,該方法調用this.stop()
以清除“暫停”(入隊)任務。
您甚至可以從任務函數內部操作隊列。 檢查: await item.action(this)
使用this
調用並提供對Queue
實例的訪問權限。 (這是可選的)。 在某些用例中, task
Fn 不應該能夠做到。
...達到文字限制:D
更多: https ://gist.github.com/exodus4d/6f02ed518c5a5494808366291ff1e206
您可以保存上一個待處理的承諾,在調用 next fetch
之前等待它。
// fake fetch for demo purposes only const fetch = (url, options) => new Promise(resolve => setTimeout(resolve, 1000, {url, options})) // task executor const addTask = (() => { let pending = Promise.resolve(); const run = async (url, options) => { try { await pending; } finally { return fetch(url, options); } } // update pending promise so that next task could await for it return (url, options) => (pending = run(url, options)) })(); addTask('url1', {options: 1}).then(console.log) addTask('url2', {options: 2}).then(console.log) addTask('url3', {options: 3}).then(console.log)
這是我之前做的一個, 也可以在打字稿中找到
function createAsyncQueue(opts = { dedupe: false }) { const { dedupe } = opts let queue = [] let running const push = task => { if (dedupe) queue = [] queue.push(task) if (!running) running = start() return running.finally(() => { running = undefined }) } const start = async () => { const res = [] while (queue.length) { const item = queue.shift() res.push(await item()) } return res } return { push, queue, flush: () => running || Promise.resolve([]) } } // ----- tests below 👇 const sleep = ms => new Promise(r => setTimeout(r, ms)) async function test1() { const myQueue = createAsyncQueue() myQueue.push(async () => { console.log(100) await sleep(100) return 100 }) myQueue.push(async () => { console.log(10) await sleep(10) return 10 }) console.log(await myQueue.flush()) } async function test2() { const myQueue = createAsyncQueue({ dedupe: true }) myQueue.push(async () => { console.log(100) await sleep(100) return 100 }) myQueue.push(async () => { console.log(10) await sleep(10) return 10 }) myQueue.push(async () => { console.log(9) await sleep(9) return 9 }) // only 100 and 9 will be executed // concurrent executions will be deduped console.log(await myQueue.flush()) } test1().then(test2)
示例用法:
const queue = createAsyncQueue()
const task1 = async () => {
await fetchItem()
}
queue.push(task1)
const task2 = async () => {
await fetchItem()
}
queue.push(task2)
// task1 will be guaranteed to be executed before task2
https://stackoverflow.com/a/71239408/8784402
const fastQueue = async <T, Q>(
x: T[],
threads: number,
fn: (v: T, i: number, a: T[]) => Promise<Q>
) => {
let k = 0;
const result = Array(x.length) as Q[];
await Promise.all(
[...Array(threads)].map(async () => {
while (k < x.length) result[k] = await fn(x[k], k++, x);
})
);
return result;
};
const demo = async () => {
const wait = (x: number) => new Promise(r => setTimeout(r, x, x))
console.time('a')
console.log(await fastQueue([1000, 2000, 3000, 2000, 2000], 4, (v) => wait(v)))
console.timeEnd('a')
}
demo();
我認為有一個簡單的解決方案,如下所示。
class AsyncQueue {
constructor() {
this.promise = Promise.resolve()
}
push = (task) => {
this.promise = this.promise.then(task)
}
}
let count = 0
let dummy = () =>
new Promise((res) => {
const ms = 400 + Math.ceil(1200 * Math.random())
console.log('will wait', ms, 'ms')
setTimeout(res, ms)
})
const foo = async (args) => {
const s = ++count
console.log('start foo', s)
await dummy()
console.log('end foo', s)
}
console.log('begin')
const q = new AsyncQueue()
q.push(foo)
q.push(foo)
q.push(foo)
q.push(foo)
console.log('end')
對於您的情況,您可以執行以下操作:
const q = new AsyncQueue()
const addTask = (url, options) => {
q.push(() => fetch(url, options))
}
如果你想處理一些結果:
const q = new AsyncQueue()
const addTask = (url, options, handleResults) => {
q.push(async () => handleResults(await fetch(url, options)))
}
不確定性能,我只是認為這是一個快速清潔的解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.