Promise.all同時制限

2877 ワード

背景


通常、コードが複数の非同期処理の後に実行されることを保証する必要があります.
Promise.all(promises: []).then(fun: function);
Promise.allは、promises配列内のすべてのpromiseオブジェクトがresolve状態に達してから、thenコールバックを実行することを保証することができる.
この場合、promises配列の各オブジェクトがhttpリクエストである場合、または各オブジェクトに複雑な呼び出し処理が含まれている場合を考慮します.このような対象は数十万個ある.
瞬時に数十万httpリクエストを発行したり(tcp接続数が不足して待機したり)、無数の呼び出しスタックが積み上げられてメモリがオーバーフローしたりする場合があります.
この場合、Promise.allの同時制限を考慮する必要があります.Promise.all同時制限とは、時刻ごとに同時実行されるpromiseの数が固定されているか、最終的な実行結果が元のPromise.allと一致しているかを指す.

インプリメンテーション


promiseはPromise.allを呼び出すために実行されるのではなく、promiseオブジェクトをインスタンス化する際に実行されることを知っています.この点を理解した上で、同時制限を実現するにはpromiseインスタンス化から始めるしかありません.
すなわち,promises配列を生成する制御権を,同時制御ロジックに渡す.
ここでは、この機能を一歩一歩実現するつもりはありません.npmには、async-pool、es 6-promise-pool、p-limitなど、この機能を実現するサードパーティ製のパッケージがたくさんあります.ここでは、async-poolのコードを直接持って実現原理を分析します.
コードは簡単で、不要なコードを消して、自分のコメントを加えて、大体の内容は以下の通りです.
function asyncPool(poolLimit, array, iteratorFn) {
    let i = 0;
    const ret = [];
    const executing = [];
    const enqueue = function () {
        //  ,array 
        if (i === array.length) {
            return Promise.resolve();
        }
        //  enqueue, promise
        const item = array[i++];
        const p = Promise.resolve().then(() => iteratorFn(item, array));
        //  promises 
        ret.push(p);
        // promise , executing 
        const e = p.then(() => executing.splice(executing.indexOf(e), 1));
        //  executing , promise
        executing.push(e);
        //  Promise.rece, executing promise poolLimit, promise 
        let r = Promise.resolve();
        if (executing.length >= poolLimit) {
            r = Promise.race(executing);
        }
        //  , array
        return r.then(() => enqueue());
    };
    return enqueue().then(() => Promise.all(ret));
}

promiseに再帰を加えるので、コード注釈には実行順序をあまり表記しませんが、大まかな論理は以下のようにまとめることができます.
  • は、arrayの第1の要素から始まる、promiseのオブジェクトを初期化するとともに、実行中のpromise
  • を1つのexecuting配列で保存する.
  • promiseは、poolLimt
  • に達するまで初期化され続けた.
  • は、Promise.raceを用いる、executingにおけるpromiseの実行状況を取得し、1つのpromiseの実行が完了すると、promiseの初期化を継続し、executingにおける
  • を入れる.
  • すべてのpromiseが実行済みであり、呼び出しPromise.all
  • を返す.
    使用方法は次のとおりです.
    const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
    return asyncPool(2, [1000, 5000, 3000, 2000], timeout).then(results => {
        ...
    });

    まとめ


    promise同時制限とは,実はpromiseを制御するインスタンス化が根源的である.サードパーティ関数を介している場合はpromiseを作成する制御権をサードパーティに渡せばよい.
    しかし、このような実現効果は、本質的にPromise.allを捨てて別の道を切り開いた.だからある日promise標準がこの機能を提供することを期待しています