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

推薦する

Springboot および Vue プロジェクトの Docker デプロイメントの実装手順

目次A. SpringbootプロジェクトのDockerデプロイメント1. Springbootプロ...

MySQL のマスタースレーブレプリケーションと読み取り書き込み分離の原理と使用法の詳細な説明

この記事では、例を使用して、MySQL マスター/スレーブ レプリケーションと読み取り/書き込み分離...

MySQLインストーラがコミュニティモードで実行されている場合の解決策

今日、リモートデスクトップを実行してログインしているときにこのプロンプトを見つけました「MySQL ...

docker で Apollo をデプロイする詳細なチュートリアル

1. はじめにここでは apollo について詳しく説明しません。公式サイト https://git...

HTML 基本要約推奨事項 (タイトル)

HTML: タイトル見出しは <h1> - <h6> などのタグによって定...

Nginx リバースプロキシの例の詳細な説明

1. リバースプロキシの例1 1. 効果を達成する(1)ブラウザを開き、www.123.comと入力...

Vueはel-tableを使用して列と行を動的に結合します

この記事の例では、el-tableを使用して列と行を動的にマージするVueの具体的なコードを参考まで...

VmWareでcentos7をインストールするときにインターネットにアクセスできない問題の解決策

Centos7 のインストール時に VmWare がインターネットにアクセスできない場合はどうすれば...

Linux md5sumコマンドの使い方

01. コマンドの概要md5sum - MD5検証コードを計算して検証するmd5sum コマンドは、...

grep を使用して MySQL エラー ログ情報を取得する方法の詳細な説明

MySQL のメンテナンスを容易にするために、エラー情報を収集するためのインターフェースを提供するス...

Linux (Ubuntu 18.04) に Anaconda をインストールする詳細な手順

Anaconda は、大規模なデータ処理、予測分析、科学計算のための最も人気のある Python デ...

Apacheドメイン名設定の落とし穴の詳細な説明

私はApacheを使ったことがありません。仕事を始めてからはずっとnginxを使っていました(運用保...

フロントエンド開発に必要な共通ツール機能のまとめ

1. 時刻の書式設定とその他の方法moment.jsライブラリファイルの使用をお勧めします2. テン...

Ubuntu 20.04 ファイアウォール設定の簡単なチュートリアル (初心者)

序文ますます便利になった今日のインターネット社会では、さまざまなインターネット ランサムウェア ウイ...

MySQL 8.0.12 のインストールと設定のチュートリアル

この記事はMySQL 8.0.12のインストールと設定に関する詳細なチュートリアルを記録しています。...