JavaScript で同時実行制御を実装する方法

JavaScript で同時実行制御を実装する方法

1. 同時実行制御の概要

実行する保留中のタスクが 6 つあり、同時に実行できるタスクの数を制限したい、つまり同時に実行できるタスクは最大 2 つにしたいとします。実行中のタスク リスト内のいずれかのタスクが完了すると、プログラムは自動的に ToDo タスク リストから新しい ToDo タスクを取得し、そのタスクを実行中のタスク リストに追加します。上記のプロセスをより直感的に理解できるように、アバオ兄弟は次の 3 つの図を特別に描きました。

1.1 フェーズ1

1.2 フェーズ2

1.3 フェーズ3

さて、同時実行制御を紹介した後、Github の async-pool ライブラリを使用して、非同期タスク同時実行制御の具体的な実装を紹介します。

https://github.com/rxaviers/async-pool

ネイティブ ES6/ES7 を使用して、制限された同時実行性で複数の promise を返す非同期関数を実行します。

2. 同時実行制御の実装

async-pool ライブラリは、ES7 と ES6 という 2 つの異なる実装バージョンを提供します。特定の実装を分析する前に、その使用方法を見てみましょう。

2.1 asyncPoolの使用

const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
asyncPool(2, [1000, 5000, 3000, 2000], タイムアウトを待機します。)

上記のコードでは、async-pool ライブラリによって提供される asyncPool 関数を使用して、非同期タスクの同時制御を実装しています。 asyncPool 関数のシグネチャは次のとおりです。

関数 asyncPool(poolLimit, 配列, iteratorFn){ ... }

この関数は 3 つのパラメータを受け取ります。

  • poolLimit (数値型): 制限する同時接続数を示します。
  • 配列(配列型):タスク配列を表します。
  • iteratorFn (関数型): 各タスク項目を処理するために使用される反復関数を表します。この関数は、Promise オブジェクトまたは非同期関数を返します。

上記の例では、asyncPool 関数を使用した後の対応する実行プロセスは次のようになります。

const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
asyncPool(2, [1000, 5000, 3000, 2000], タイムアウトを待機します。)
// イテレータを呼び出す (i = 1000)
// イテレータを呼び出す (i = 5000)
// プールの制限 2 に達しました。早い方の完了を待ちます...
// 1000 フィニッシュ
// イテレータを呼び出す (i = 3000)
// プールの制限 2 に達しました。早い方の完了を待ちます...
// 3000 フィニッシュ
// イテレータを呼び出す (i = 2000)
// 反復処理が完了しました。実行中の反復処理が完了するまで待機します...
// 5000 フィニッシュ
// 2000 終了
// 解決され、結果は指定された配列の順序 `[1000, 5000, 3000, 2000]` で渡されます。

上記のコメントを観察することで、asyncPool 関数内の制御フローを大まかに理解できます。次に、asyncPool 関数の ES7 実装を分析してみましょう。

2.2 asyncPool ES7 実装

非同期関数 asyncPool(poolLimit, 配列, iteratorFn) {
  const ret = []; // すべての非同期タスクを保存 const performing = []; // 実行中の非同期タスクを保存 for (const item of array) {
    // iteratorFn 関数を呼び出して非同期タスクを作成します const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p); // 新しい非同期タスクを保存 // poolLimit 値がタスクの総数以下の場合は、同時実行制御を実行します if (poolLimit <= array.length) {
      // タスクが完了したら、実行中のタスクの配列から完了したタスクを削除します。const e = p.then(() => execute.splice(executing.indexOf(e), 1));
      実行中の非同期タスクを保存する if (executing.length >= poolLimit) {
        await Promise.race(executing); // より高速なタスクが完了するまで待機します }
    }
  }
  Promise.all(ret) を返します。
}

上記のコードでは、Promise.all 関数と Promise.race 関数の機能が最大限に活用され、ES7 で提供される async await 機能と組み合わされて、最終的に同時実行制御機能が実現されています。 await Promise.race(executing); ステートメントを使用して、実行中のタスクのリスト内のより高速なタスクが完了するまで待機してから、次のループに進みます。

asyncPool ES7 の実装は比較的シンプルです。次に、async await 機能を使用せずに同じ機能を実現する方法を見てみましょう。

2.3 asyncPool ES6 実装

関数 asyncPool(poolLimit, 配列, iteratorFn) {
  i = 0 とします。
  const ret = []; // すべての非同期タスクを保存 const performing = []; // 実行中の非同期タスクを保存 const enqueue = function () {
    if (i === 配列の長さ) {
      Promise.resolve() を返します。
    }
    const item = array[i++]; // 新しいタスク項目を取得します。const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p);

    r = Promise.resolve() とします。

    // poolLimit値がタスクの総数以下の場合、同時実行制御を実行します。if (poolLimit <= array.length) {
      // タスクが完了したら、実行中のタスクの配列から完了したタスクを削除します。const e = p.then(() => execute.splice(executing.indexOf(e), 1));
      実行中.push(e);
      実行中の長さ >= poolLimit の場合 {
        r = Promise.race(実行中);
       }
    }

     // 実行中のタスク リスト内のより高速なタスクが完了すると、配列から新しいタスクが取得されます。array return r.then(() => enqueue());
  };
  enqueue() を返します。その後 (() => Promise.all(ret));
}

ES6 実装では、コア制御ロジックは内部エンキュー関数を通じて実装されます。 Promise.race(executing) によって返された Promise オブジェクトが完了すると、enqueue 関数が呼び出され、配列から新しい ToDo タスクが取得されます。

3. アバオ兄弟は何か言いたいことがある

asyncPool ライブラリの ES7 および ES6 実装では、Promise.all 関数と Promise.race 関数を使用しました。その中でも、手書きのPromise.allは面接でよく聞かれる質問です。この機会を利用して、アバオ兄弟はみんなと一緒に Promise.all と Promise.race 関数の簡単なバージョンを作成します。

3.1 手書きの約束.all

Promise.all(iterable) メソッドは、Promise オブジェクトを返します。すべての入力 Promise オブジェクトの状態が解決されると、返される Promise オブジェクトは、解決後の各 Promise オブジェクトの結果を配列の形式で返します。入力されたプロミス オブジェクトのステータスが拒否されると、返されるプロミス オブジェクトは対応するエラー メッセージを拒否します。

Promise.all = 関数 (イテレータ) {
  新しい Promise を返します ((resolve, reject) => {
    if (!イテレータ || イテレータの長さ === 0) {
      解決する([]);
    } それ以外 {
      let count = 0; // すべてのタスクが完了したかどうかを判断するために使用されるカウンター let result = []; // 結果配列 for (let i = 0; i < iterators.length; i++) {
        // iterators[i] は通常のオブジェクトである可能性があることを考慮して、Promise オブジェクトとしてパッケージ化されます Promise.resolve(iterators[i]).then(
          (データ) => {
            result[i] = data; // 対応する結果を順番に保存します // すべてのタスクが完了したら、結果を均一に返します if (++count === iterators.length) {
              解決(結果);
            }
          },
          (エラー) => {
            reject(err); // いずれかの Promise オブジェクトの実行に失敗した場合は、reject() メソッドが呼び出されます。 return;
          }
        );
      }
    }
  });
};

Promise.all の標準実装では、そのパラメータは配列、文字列、または Set などの反復可能なオブジェクトであることに注意してください。

3.2 手書きの約束.race

Promise.race(iterable) メソッドは、Promise オブジェクトを返します。イテレータ内の Promise オブジェクトが解決または拒否されると、返される Promise オブジェクトは対応する値を解決または拒否します。

Promise.race = 関数 (イテレータ) {
  新しい Promise を返します ((resolve, reject) => {
    for (イテレータの定数イテレータ) {
      Promise.resolve(イテレータ)
        .then((res) => {
          解決する(res);
        })
        .catch((e) => {
          拒否する
        });
    }
  });
};

この記事では、Abao Ge が async-pool 非同期タスク同時実行制御の具体的な実装を詳細に分析し、同時に、誰もが async-pool のコアコードをよりよく理解できるようにします。最後に、アバオ兄弟は全員を率いて、Promise.all 関数と Promise.race 関数のシンプルなバージョンを作成しました。実際、Promise.all 関数に加えて、Promise.all の問題を解決するために使用される Promise.allSettled という別の関数があります。興味のある友人は自分でそれを勉強することができます。

IV. 参考資料

Github - 非同期プール
MDN - Promise.all
MDN - プロミスレース
MDN - Promise.allSettled

上記は、JavaScript で並行処理制御を実装する方法の詳細です。JavaScript の並行処理制御の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • JavaScript で Promise を使用して同時リクエスト数を制御する方法
  • ByteDance インタビュー: JS を使用して Ajax 同時リクエスト制御を実装する方法
  • JavaScript/TypeScript で同時リクエスト制御を実装するためのサンプルコード
  • JavaScript を使用して同時実行制御を実装するためのサンプル コード
  • js非同期インターフェースの同時実行数を制御する方法の例
  • Nodejs クローラーの高度なチュートリアル 非同期同時実行制御
  • Nodejs 実践体験: 同時実行を制御するイベントプロキシモジュール

<<:  MySQL FAQ シリーズ: ibdata1 ファイルのサイズが突然増加しないようにする方法

>>:  Nginx で limit_req_zone を使用して同じ IP へのアクセスを制限する方法

推薦する

IE8 と Chrome でテーブルの幅を修正する方法

IE8 や Chrome で上記の設定を使用すると、画面の最大幅に合わせて表示が統一され、各列の幅は...

React Fiberの仕組みの詳細な説明

目次React Fiberとは何ですか?なぜReact Fiberなのか? React Fiberは...

モバイルブラウザが位置をサポートしない場合の解決策: 修正

具体的な方法は以下の通りです。 CSSコードコードをコピーコードは次のとおりです。 .wap_bot...

MySQL OOM (メモリオーバーフロー) の解決策

OOM は「Out Of Memory」の略で、メモリオーバーフローを意味します。メモリ オーバーフ...

MySQL データベースの最適化: インデックスの実装原則と使用状況の分析

この記事では、例を使用して、MySQL データベースの最適化のためのインデックス実装の原則と使用方法...

Pure CSS と Flutter はそれぞれブリージング ライト効果を実現します (サンプル コード)

前回、非常に熱心なファンから、月を呼吸する光の効果にできるかどうか尋ねられました。月の大きさの写真が...

MySQL統計の概要

MySQL は、SQL 解析とクエリ最適化のプロセスを通じて SQL を実行します。パーサーは SQ...

タオバオモールのホームページ上の大きな画像のデザイン構造に関する分析と意見(写真)

前回、Taobaoの詳細ページを分析した後(クリックして表示)、ショッピングモールの基本テンプレート...

クロスオリジン画像リソース権限(CORS 対応画像)

HTML 仕様書では、画像の crossorigin 属性が導入されています。適切なヘッダー情報 ...

JQueryはアニメーション効果の非表示と表示を実装します

この記事では、アニメーション効果の非表示と表示を実現するためのJQueryの具体的なコードを参考まで...

MySQL 5.7 に組み込まれているストレス テストの mysqlslap コマンドと構文の詳細な説明

序文mysqlslap は、MySQL サーバーへのクライアント負荷をシミュレートし、各ステージの時...

初心者向けMySQLシリーズチュートリアル

目次1. 基本概念と基本コマンド1) 基本的な概念2) 基本コマンド2. SQL文の記述順序と実行順...

CSS を使用して、画像に 3D の凸型と凹型のエフェクト (フレーム外に凸型、またはフレーム内に凹型) を実現します。

Ⅰ. 問題の説明: CSS を使用して画像の 3D 凸凹効果を実現します。 Ⅱ実施手順は以下のとお...

MySQL 8.0 における非同期レプリケーションの 3 つの方法について簡単に説明します。

この実験では、空のデータベース、オフライン、オンラインの 3 つのモードで、1 つのマスターと 2 ...

Linux の netstat コマンドの詳細な紹介

目次1. はじめに2. 出力情報の説明3. netstatの共通パラメータ4. netstatネット...