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 へのアクセスを制限する方法

推薦する

MySQL 8.0.13 手動インストールチュートリアル

この記事では、MySQL 8.0.13の手動インストールチュートリアルを参考までに紹介します。具体的...

Vueはmockjsを使用してシミュレートされたデータケースの詳細を生成します

目次プロジェクトにmockjsをインストールするVueプロジェクトでmockjsを使用する基本的なプ...

Vueはアップロードコンポーネントを実装します

目次1. はじめに2. アイデアファイルをアップロードする2つの方法3. ライフサイクル4. コード...

MySQL UNION演算子の基本知識ポイント

MySQL UNION 演算子このチュートリアルでは、MySQL UNION 演算子の構文と例を紹介...

nginxで静的リソースを公開する方法

ステップ準備した静的リソースファイルを指定されたフォルダに配置しますnginx 設定ファイルを変更す...

Linux の操作とメンテナンスの基本的なスワップ パーティションと LVM 管理のチュートリアル

目次1. スワップパーティション SWAP 1.1 スワップファイルを作成する1.2 スワップパーテ...

ページの下部にHTMLフッターを配置する簡単な方法

要件:ページ コンテンツが短く、ブラウザーの高さをサポートできない場合でも、フッターをウィンドウの下...

ネイティブ js はカスタム スクロール バー コンポーネントを実装します

この記事の例では、カスタムスクロールバーコンポーネントを実装するためのjsの具体的なコードを参考まで...

HTML DOM入門_PowerNode Javaアカデミー

DOMとは何ですか? JavaScript を使用すると、HTML ドキュメント全体を再構築できます...

MySQLトリガートリガー例の詳細な説明

目次トリガーとは何かトリガーを作成する表は次のようになります。さらにいくつかの単語を挙げます。制限と...

CSSファイルをインポートする3つの方法の詳細な説明

CSS を導入する方法には、インライン スタイル、内部スタイル シート、外部スタイル シートの 3 ...

Linux centos7 環境での MySQL インストール チュートリアル

Linux centos7 環境に MySQL をインストールする手順の詳細な紹介MySQLをインス...

nginx が複数のプロキシ層を通過して実際の送信元 IP を取得するプロセスの詳細な説明

質問Nginx は $remote_addr を実際の IP アドレスとして受け取りますが、実際には...

MySQL 5.7.17 zip インストールおよび設定チュートリアル MySQL 起動失敗の解決策

MySQL 5.7.17、現在最新バージョンのようです、ダウンロードアドレスここで、プラットフォーム...

Linux の特別な権限 SUID、SGID、SBIT の詳細な説明

序文Linux のファイルまたはディレクトリの権限については、通常の rwx 権限についてすべて知っ...