🌟

【Nuxtjs】Promise allでパフォーマンス改善について

2021/07/27に公開

背景

最近NuxtJSの公式オンラインコース を受講してパフォーマンス周りを改善する方法を勉強したので共有します。

概要

NuxtのSSRモードでページにアクセス時にasyncDataやfetchで複数のAPIリクエストを投げるのはよくあることです。APIリクエストはasync/waitで非同期処理なので、例えばA、B、Cの3つのリクエストがある場合、BはAが完了してから処理され、CはBが完了してから処理されます。リクエストが多ければ多いほどパフォーマンスに悪影響を与えてしまいます。

Promise.all でAPIリクエストを並列処理させることで、ページアクセスが早くなり、TTFBの改善を期待できます。

結論&効果

先に結論を言いますと、リクエストが多ければ多いほど、この手法の効果が顕著になります。

下記の例を見てみましょう。
下記5つのAPIリクエストがあるとして、それぞれ処理する時間は
リクエストA: 1秒
リクエストB: 2秒
リクエストC: 3秒
リクエストD: 3秒
リクエストE: 3秒

Promise.allを使う/使わない それぞれかかる時間は下記になります。

Promise.allを使わないパターン
掛かる時間はそれぞれの時間の合算値です。
A + B + C + D + E = 1 + 2 + 3 + 3 + 3 => 12秒かかります。

Promise.allを使うパターン
すべてのリクエストの最大処理時間になります。
最大値は3秒になります。

単純な例ですが、使用しないパターンより4倍早くなります。

下記の画像のの上3つのリクエストはPromise allを使ってないパターンですが、リクエストは前の処理が終わらないと次の処理はされないことがわかります。
下2つのリクエストはPromise allを使うパターンで、リクエストは並列処理になります。
SSRでサーバーからhtmlドキュメントを返されるのが早くなり、TTFBの改善ができます。

早い回線では効果はそこまで感じないかもしれませんが、dev toolのnetworkタブから回線速度を3G slowもしくは3G fastに変えれば効果をより体感できます。

改善前の例

まずは、改善前のコードを見てみましょう。

async fetch() {
    try {
      // 1個目のリクエスト
      await this.$api.cities.cityData()
        .then((response) => {
          this.cities = response.data.cities;
          this.prefecture = response.data.prefecture;
        });
            // 2個目のリクエスト
      await this.$api.metaData.getData()
        .then((response) => {
          this.metaData = response.data;
        });
            // 3個目のリクエスト
      await this.$api.searches.getConditionMaster()
      .then((response) => {
        this.conditionMaster = response.data;
      });
      
    } catch (err) {
      // エラー処理
    }
}

リクエストは3つあります。

await this.$api.cities.cityData
await this.$api.metaData.getData
await this.$api.searches.getConditionMaster

これらをPromise allで処理するように書き換えます。

改善後

async fetch() {
    // すべてのリクエストを配列としてPromise allに渡す
    await Promise.all([
      this.$api.cities.cityData(),
      this.$api.metaData.getData(),
      this.$api.searches.getConditionMaster(),
    ])
      .then((responses) => {
        this.cities = responses[0].data.cities;
        this.prefecture = responses[0].data.prefecture;
        this.metaData = responses[1].data;
        this.conditionMaster = responses[2].data;
      })
      .catch((err) => {
        // エラー処理
      });
}

リクエストを配列としてPromise.allの引数として入れて、thenブロックではAPIから返されたデータであるresponses配列を処理します。
responses配列のデータの順番はリクエスト配列と同じで、responses[0]はcityData、responses[1]はgetData、responses[2]はgetConditionMaster のデータになります。

ちなみに、リクエストのどれがエラーが起きる場合、catchブロックで処理されます。

改善後のパフォーマンス

結論で書いた通り、それぞれのリクエストが並列処理になりました。この場合、city_dataの処理時間が最大なので、トータルの処理時間は510msで、改善した効果は下記になります。

改善前: 510ms + 300ms + 99ms = 約910ms
改善後: 510ms
改善効果:速度は約56%改善されました。

最後に

Promise allは非同期処理で珍しくないメソッドですが、fetchやasyncDataで使うとリクエスト数が多いければ多いほど改善効果が体感するほど絶大です。

最後まで読んで頂きてありがとうございます。
またなにか知見が得たらシェアします。
それではまた。

Discussion