簡體   English   中英

JS 異步/等待任務隊列

[英]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()等待異步任務。

特殊行為:

  1. 每次新任務(例如fetch('url')隊時,都會調用this.dequeue()
  2. dequeue()的作用:
    1. 如果隊列為空 ➜ return false (脫離遞歸)
    2. 如果隊列忙 ➜ return false (上一個任務未完成)
    3. else ➜ 從隊列中刪除第一個任務並運行它
    4. 任務“完成”(成功或有錯誤)➜遞歸調用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中,我們可以很好地可視化我們的隊列:

在此處輸入圖像描述 (*.gif 動畫)

  • 第一行是我們的隊列實例
  • 新入隊的tasks有一個“❚❚ 等待..”期間(第 3 行)(如果隊列為空,則可能< 1ms
  • 在某些時候,它會出隊並“▶運行..”一段時間(第二行

日志輸出( console.table() ):

console.table() 用於上一頁。例子

說明:第一個task是隊列初始化后2.58msenqueue() d。 由於我們的隊列是空的,所以沒有❚❚ waiting0.04ms ➜ ~ 40μm )。 任務運行時間13.88ms ➜ 出隊


Class 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)一樣調用:

  1. let task = queue => {..}同步函數
  2. let task = async queue => {..}異步函數
  3. let task = queue => new Promise(resolve => setTimeout(resolve, 100)new Promise()

注意:我們已經用await調用了我們的任務,其中await將任務的響應包裝Promise中。 Nr 2.(異步函數),總是自己返回一個Promise ,而await調用只是將一個Promise包裝到另一個Promise中,效率稍低。 Nr 3. 很好。 返回的 Promise不會await包裹

這是異步函數的執行方式:( 來源

  1. 異步函數的結果始終是 Promise p 該 Promise 是在開始執行 async 函數時創建的。
  2. 身體被執行。 執行可以通過 return 或 throw 永久結束。 或者它可以通過 await 暫時完成; 在這種情況下,執行通常會在以后繼續。
  3. 返回 Promise 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

您可以依賴以下順序:

  1. 第 (A) 行:異步函數同步啟動。 異步函數的 Promise 是通過 return 解決的。
  2. (C) 行:繼續執行。
  3. 行 (B):Promise 解決通知異步發生。

閱讀更多:“可調用值”閱讀更多:“ 異步函數


性能限制

由於AutoQueue僅限於處理一個接一個的任務,因此它可能會成為我們應用程序的瓶頸。 限制因素是:

  1. 每次任務數: ➜ new enqueue() d 任務的頻率。
  2. 每個任務的運行時間 ➜ dequeue()中的阻塞時間,直到任務完成

1. 每次任務

這是我們的責任! 我們可以隨時獲取queue的當前大小: size = queue.size 您的外部腳本需要一個穩定增長的隊列的“故障轉移”案例(檢查“堆疊wait時間”部分)。

您想避免像這樣的“隊列溢出”,平均/平均等待時間會隨着時間的waitTime而增加。

+-------+----------------+----------------+----------------+----------------+
| tasks | enqueueMin(ms) | enqueueMax(ms) | runtimeMin(ms) | runtimeMax(ms) |
| 20    |              0 |            200 |             10 |             30 |
+-------+----------------+----------------+----------------+----------------+

在此處輸入圖像描述

在此處輸入圖像描述

  • ➜ 任務20/20等待195ms ,直到 exec 開始
  • ➜ 從我們最后一個任務被隨機入隊的時間開始,它需要另外+ ~232ms ,直到所有任務都被解決。

2. 每個任務的運行時間

這個比較難對付。 (等待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.size22

  • 等待任務分配不均。 開始后甚至還有一些空閑部分。

    由於隨機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()輸出構建圖表:

在此處輸入圖像描述

除了taskwait時間,我們可以看到隨機的 [10-30ms] runtime和 3 條曲線,代表當前queue.size ,記錄在task的時間..

  • .. enqueued()
  • ..開始運行。 dequeue()
  • .. 任務完成(就在下一個dequeue()之前)

再運行 2 次進行比較(類似趨勢):

  • 圖表運行 2: https ://i.imgur.com/EjpRyuv.png
  • 圖表運行 3: https ://i.imgur.com/0jp5ciV.png

我們能找到彼此之間的依賴關系嗎?

如果我們能找到這些記錄的圖表線之間的關系,它可能會幫助我們了解queue隨時間的行為(➜ 不斷填充新任務)。

Exkurs:什么是關系? 我們正在尋找一個方程,將wait ms曲線投影3 個queue.size記錄之一。 這將證明兩者之間的直接依賴關系。

在上次運行中,我們更改了啟動參數:

  • 任務數: 2001000 (5x)

  • msEnqueueMax: 4000ms20000ms (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 個未知變量: Xwait 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. 方程中的淺綠色旁邊的深綠色水平路徑曲線)。

我們現在可以做的事情:

  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
  2. 我們可以在我們的圖表上繪制一條新的圖表曲線,並檢查它與記錄的wait ms (橙色)相比有多接近

在此處輸入圖像描述 (互動圖表: https ://datawrapper.dwcdn.net/LFp1d/2/)

結論

考慮到我們的任務的運行時間可以通過某種方式確定這一事實,我們可以在任務入隊之前預測它的wait時間。 因此,它在您將相同類型/功能的任務排入隊列的情況下效果最佳: 用例:一個AutoQueue實例充滿了 UI 組件的渲染任務。 渲染時間可能不會對聊天有太大影響(與fetch()相比)。 也許您在地圖上渲染 1000 個位置標記。 每個標記都是一個帶有render() Fn 的類的實例。


提示

  1. Queues用於各種任務。 ➜ 為不同類型的邏輯實現專用的Queue類變體(不要在一個類中混合不同的邏輯)
  2. 檢查所有可能被排入同一個AutoQueue實例(現在或將來)的task ,它們可能被所有其他的阻塞。
  3. AutoQueue不會改善運行時間,充其量不會降​​低。
  4. 對不同的Task類型使用不同的AutoQueue實例。
  5. 監控AutoQueue的大小,特別是 ..
    • .. 大量使用(頻繁使用enqueue()
    • .. 在長時間未知task運行時
  6. 檢查您的錯誤處理。 由於您的tasks中的錯誤只會reject他們在入隊時返回的承諾( promise = queue.enqueue(..) )並且不會停止出隊過程。 你可以處理錯誤..
    • .. 在你的任務中 ➜ `try{..} catch(e){ .. }
    • .. 在它之后(在下一個之前)➜ return new Promise()
    • ..“異步”➜ queue.enqueue(..).catch(e => {..})
    • ..“全局”➜ AutoQueue類中的錯誤處理程序
  7. 根據Queue的實現,您可能會看到queue.size 一個包含 1000 個任務的Array不如我在最終代碼中使用的分散數據結構(如“雙鏈表”)有效。
  8. 避免遞歸地獄。 (可以使用enqueue()其他人的tasks ) - 但是,調試AutoQueue並不有趣,其中tasks是在async環境中由其他人動態enqueue() e..
  9. 乍一看, 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
);

由於我們的Queuedump ,並且只是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.

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