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

推薦する

React+Antdはテーブルの追加、削除、変更の例を実装します

目次テーブル/index.jsテーブル/モデル/index.jsテーブル/モデル/モジュール/bas...

CentOS 7 で Python を 3.6.6 にアップグレードした後に発生する yum エラー問題の解決方法の概要

最近、テスト サーバーのオペレーティング システムを Cent0S 7.5 にアップグレードし、Py...

Sitemesh チュートリアル - ページ装飾技術の原理と応用

1. 基本概念1. Sitemeshはページ装飾技術です。 1 : フィルターを通してページアクセス...

DockerコンテナのIPアドレスを取得する方法の詳細な説明

1.コンテナに入った後 /etc/hosts を cat するコンテナ自体の IP アドレスと (-...

Windows 上で Nginx+Tomcat クラスタを実装するプロセスの分析

導入: Nginx (エンジン エックスと同じ発音) は、BSD のようなプロトコルに基づいてリリー...

リバースプロキシ設定を実装するためのユニバーサルnginxインターフェース

1. プロキシサーバーとは何ですか?プロキシ サーバーは、クライアントが要求を送信すると、それを直接...

Flexレイアウトを使用してdiv内のサブ要素を垂直方向に中央揃えする例

1. Flex は Flexible Box の略で、「柔軟なレイアウト」を意味し、ボックス モデル...

検証コードケースのjs実装

この記事の例では、検証コードを実装するためのjsの具体的なコードを参考までに共有しています。具体的な...

MySQL 5.7 クラスタ構成手順

目次1. サーバーAのmy.cnfファイルを変更する2. サーバーBのmy.cnfファイルを変更する...

InnoDB エンジンのパフォーマンスを最適化するための my.cnf パラメータ構成

私はインターネット上で数え切れないほどの my.cnf 構成を読みましたが、言及されている構成のほと...

MySql 8.0.16-win64 インストール チュートリアル

1. ダウンロードしたファイルを以下のように解凍します。 。 2. 環境変数に解凍ディレクトリを追加...

数十億のデータに対するMySQLページングの最適化に関する簡単な説明

目次背景分析するデータシミュレーション1. 従業員テーブルと部門テーブルの2つのテーブルを作成します...

MySQL で期限切れのデータレコードを定期的に削除する簡単な方法

1. MySQL に接続してログインしたら、まず MySQL でイベント機能が有効になっているかどう...

Zabbixで監視する必要があるホストを追加するための詳細な手順

監視ホストの追加ホスト 192.168.179.104 が zabbix 監視項目に追加されます (...