Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

Nuxt.jsのasyncData関数をラップしてエラーハンドリングを共通化する

この記事はモバイルファクトリー Advent Calendar 2020 9日目の記事です。

こんにちは、ブロックチェーンチームの新卒エンジニアid:charinesです。

Nuxt.jsにおけるasyncDataの役割

ブロックチェーンチームでは、Nuxt.jsのサーバーサイドレンダリング機能を用いた開発を行っています。asyncDataはページの読み込み時に、返されたPromiseの値をコンポーネントのdataにマージするためのフックで、ページの移動やエラーページの表示はPromiseの解決を待って行われます。

問題

asyncDataはページコンポーネント毎に定義されるため、読み込み時のエラーハンドリングなどの処理が全ページで共通であったとしても、各ページコンポーネントにその処理を記述しなければなりません。

具体例として、サーバーサイドで実行されたasyncData内で例外が発生した場合にエラーページを表示するには、asyncDataの第一引数のオブジェクトに定義されたerror関数を呼び出す必要があります。次に示すのはasyncData内でAPIからエラーレスポンスが返された際にエラーページを表示する処理です。

Vue.extend({
  async asyncData({ app, error }) {
    try {
      // app.$api.getUser() が返すプロミスは
      // statusとmessageをプロパティとして持つ例外でリジェクトされることがある
      const user = await app.$api.getUser();
      return { user };
    } catch (err) {
      if (process.server) {
        // サーバーサイドで実行されている場合はerrorを呼び出してエラーページを表示する
        error({
          statusCode: err.response ? err.response.status : 500,
          message: err.message,
        });
        return;
      }
      throw err;
    }
  },
  data: () => ({
    user: undefined,
  }),
});

この例ではapp.$api.getUserという非同期関数の解決した値をuserとしてdataにマージします。 ここで、このコードを修正して「クライアントサイドでステータスコード401のHTTPレスポンスを受け取った例外が発生した場合はリロードする」という処理を入れることにしました。しかしこのような変更を行う場合、先述の通りasyncDataはページコンポーネント毎に定義されているため、全てのページコンポーネントに修正を行う必要があります。*1  

やったこと

全てのページで共通するasyncDataのエラーハンドリングを一箇所のコードにまとめるために、asyncDataを生成する関数を書きました。以下が実際のコードです。

export function createAsyncData(asyncData) {
  return async (context) => {
    try {
      // asyncDataはページ固有の処理を行う関数
      const data = await asyncData(context);
      return data;
    } catch (err) {
      const statusCode = err.response ? err.response.status : 500;
      if (process.server) {
        context.error({
          statusCode,
          message: err.message,
        });
        return;
      }
      if (statusCode === 401) {
        location.href = context.route.path;
        // リダイレクトが完了するまでにエラーページが描画されないようプロミスを待機させる
        await Promise.race([]);
      }
      throw err;
    }
  };
}

この関数はページ固有の処理を行う関数を引数として受け取り、受け取った関数の実行とエラーハンドリングを行う新たな関数を返します。各ページコンポーネントではこの関数を以下のように使用します。

Vue.extend({
  asyncData: createAsyncData(async ({ app }) => {
    const user = await app.$api.getUser();
    return { user };
  }),
  data: () => ({
    user: undefined,
  }),
});

これでエラーハンドリングなどの全ページで共通の処理をページコンポーネント毎に書く必要がなくなり、変更が容易なコードになりました。

まとめ

asyncData関数を生成する関数を書いて全ページで共通の処理をページコンポーネントの実装から分離することで、この関数を修正するだけで全ページのエラーハンドリングを修正することができるようになりました。

明日の記事は id:tsukumaru さんです!

*1:Nuxt 2.12以降はfetchを利用することができます。fetchを使う場合もエラーハンドリングなどの処理はasyncDataと同様です。