技術共有

フロントエンドが同時リクエストを制御する方法

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

フロントエンドが同時リクエストを制御する必要があるのは、必要なデータのリクエストに複数回かかる場合です。たとえば、インターフェイスが一度に大量のデータを返す場合、ブラウザはレンダリング中にスタックしたりクラッシュしたりする可能性があります。このとき、スタックやクラッシュを回避するために、バッチで 6 つのリクエストを同時に発行できます。 。

では、フロントエンドは同時リクエストをどのように制御するのでしょうか?

同時リクエストのフロントエンド制御に関する重要なアイデア

たとえば、20 件のリクエストがある場合、次のようにする必要があります。 3 最初のグループがリクエストを完了すると、2 番目のグループがリクエストされ、以下同様に続きます。

重要なアイデアは、リクエスト メソッドとリクエスト パラメータを配列に格納し、一度に 3 つずつリクエストし、リクエストが完了した後に次の 3 つをリクエストすることです。リクエストの各グループが返された後、結果が保存され、すべてのリクエストが返された後、すべての結果が返されます。

APIデザイン

pControl : 同時リクエスト コントローラー。同時実行の最大数を渡します。
add : リクエストとパラメータを追加します。
start : リクエストの開始、Promise の返却、リクエスト完了後のパス.then すべての結果を取得します。

コード

function pControl(limit) {
  const taskQueue = [] // {task: Function, params: any[]}[]
  return {
    add,
    start
  }

  function add(task, params) {
    taskQueue.push({
      task,
      params
    })
  }

  function start() {
    return runAllTasks()
  }

  function runAllTasks() {
    const allResults = []
    return new Promise((resolve) => {

      runTask()

      function runTask() {
        if (taskQueue.length === 0) {
          // 递归结束
          return resolve(allResults)
        }
        const needRunSize = Math.min(taskQueue.length, limit)
        const tasks = taskQueue.splice(0, needRunSize)
        const promises = tasks.map(({
          task,
          params
        }) => task(params))
        Promise.all(promises).then((resList) => {
          allResults.push(...resList)
          // NOTE 递归调用的位置很关键
          runTask()
        })
      }
    })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

キーコードの解釈

  • pControl: この関数は、次の内容を含むオブジェクトを返します。 add そしてstart 2つの方法、add タスクとパラメータを追加するために使用されます。start リクエストを開始するために使用される、クロージャです。

  • runAllTasks: を返しますpromise、そして、new Promise内部的に再帰的に実行されるrunTask、runTask が渡されるPromise.all 同時リクエストを実行するPromise.all().then() もう一度電話してくださいrunTask、返される要求の 1 セットを実装し、次に 2 番目の要求セットを実行します。

集団待機を実現する鍵となるのは、 .then 再帰呼び出し。

考えてみます: runAllTask​​s はループを使用して実装できますか?

はい、使用する必要があります async 和 for 循环 + await

async function runAllTasks2() {
  const allResults = []
  const groupArr = []
  let startIndex = 0
  // 划分分组
  while (startIndex < taskQueue.length) {
    const arr = taskQueue.slice(startIndex, startIndex + limit)
    groupArr.push(arr)
    startIndex += limit
  }
  for (let index = 0; index < groupArr.length; index++) {
    const pList = groupArr[index].map(({
      task,
      params
    }) => task(params))
    const res = await Promise.all(pList)
    allResults.push(...res)
  }
  return allResults
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

for ループ内では使用できません .then そうでない場合、次のループは前のループを待機しません。

使用 for of 反復実装:

async function runAllTasks2() {
  const allResults = []
  const groupArr = []
  let startIndex = 0
  // 划分分组
  while (startIndex < taskQueue.length) {
    const arr = taskQueue.slice(startIndex, startIndex + limit)
    groupArr.push(arr)
    startIndex += limit
  }
  // 迭代分组
  const it = groupArr.entries()
  for (const [key, value] of it) {
    const pList = value.map(({
      task,
      params
    }) => task(params))
    const res = await Promise.all(pList)
    allResults.push(...res)
  }
  return allResults
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Loop と Promise を併用するにはどうすればよいですか?

forwhilefor...of 命令型ループ構造の場合、ループ内で待機効果を実現したい場合は、次を使用する必要があります。async ループ内の関数ラッパーawait 、故障中.then

forEachmapfilter これらの関数ループ構造は同期的であり、待機をサポートしていないため、これらの関数ループ構造は待機効果をサポートしません。

async そして循环 + await 組み合わせてループ間の待機効果を実現します。

promise.then そして递归 組み合わせてループ間の待機効果を実現します。

APIを改良して使いやすくする

  1. デフォルトパラメータを設定します: givepControl適切なデフォルト値を設定します。6, 同じドメイン名が入っているため、ブラウザの同時リクエストは6です。
  2. コールバックの開始: コールバックを通じて、各グループのリクエスト結果を取得し、現在完了しているリクエストの数を知ることができます。

これら 2 つの改善点は簡単です。まず使用法を見てみましょう:

const asyncTaskControl = pControl() // 默认 6 
asyncTaskControl.add(task, params1)
asyncTaskControl.add(task, params2)
// ...
asyncTaskControl.add(task, params10)

asyncTaskControl.start((res, doneSize) => {
  // 获取每组请求的结果 和当前完成了多少请求
  console.log(res) // [{index:number,result:data}] 
  console.log(doneSize)
}).then(allResults => {
  // 所有请求结果
  console.log(allResults)
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

開始コールバックは何をするのでしょうか?

ユーザーにとって、現在の同時リクエストの結果を取得し、完了の進行状況を計算するのに便利です。

上記の関数をカプセル化すると、 p-control npmパッケージのリリース

npm: pコントロール

アクセス可能な npm i p-control ダウンロードして使用してください。

まとめ

  • .then 再帰と組み合わせて、非同期タスク間の待機を実現します。
  • forwhileループを待って、async + await非同期タスク間の待機を実現するために組み合わせて使用​​されます。
  • 使用Promise.all複数の非同期タスクを実装して、同時に実行します。