Nuxt3のuseAsyncDataを使いやすくしてみた

useAsyncDataとは

Nuxt3から標準で提供されているAPI。 非同期処理に関する状態管理を行ってくれる。

nuxt.com

1秒後に50%の確率で成功する非同期関数をuseAsyncDataで使ってみた例

const { data, status, error, refresh, execute, clear } = useAsyncData(
  'key',
  () => {
    return new Promise<string>((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.5) {
          resolve('Success');
        } else {
          reject('Failed');
        }
      }, 1000);
    });
  }
);

引数

  • key: string
    • 第一引数
    • 非同期関数の返り値をキャッシュするためのキー
  • handler: (ctx?: NuxtApp) => Promise<T>
    • 第二引数
    • 非同期関数
  • options: AsyncDataOptions<T>
    • 第三引数(省略可能)
    • オプション

引数に関する詳細はuseAsyncData > Params

返り値

  • data: Ref<T | null>
    • 非同期関数の実行結果を保持する
  • status: Ref<'idle' | 'loading' | 'success' | 'error'>
    • 非同期関数の実行状態を保持する
  • error: Ref<Error | null>
    • 非同期関数の実行エラーを保持する
  • refresh: (opts?: AsyncDataExecuteOptions) => Promise<void>
    • 非同期関数を実行する
  • execute: (opts?: AsyncDataExecuteOptions) => Promise<void>
    • 非同期関数を実行する
  • clear: () => void

ここまでがuseAsyncDataの基本的な説明。

使いやすくしてみた

提供されているuseAsyncDataはあくまで非同期処理に関する状態管理を行うことを目的としているので、非同期処理周辺までカバーされていない。

どのようなアプリにも汎用的に使えるようにしているので十分すぎるくらい機能は提供されている。

ただアプリを作っていると非同期処理の開始前、成功時、エラー時、終了時に処理を行いたいケースが多くなる。

特にAPIリクエストするような非同期処理であれば、開始前にフォームのバリデーションエラーをリセットしたり、成功やエラー時にその旨を表すUIを描画したりすると思う。

実装

基本的にはuseAsyncDataと同じ引数と返り値にしている。

追加したのはAsyncFetchOptions という開始前、成功時、エラー時、終了時に処理をオプションとして渡せるようにした。

それらのオプションとhandlerを組み合わせて一つの非同期関数としてuseAsyncDataに渡すようにした。

こうすることによって各タイミングで設定した処理を実行することができるようになった。

import type { AsyncDataOptions } from '#app';

type AsyncFetchOptions<T> = {
  onStart?: () => void | Promise<void>;
  onSuccess?: (data: T) => void | Promise<void>;
  onError?: (error: unknown) => void | Promise<void>;
  onFinally?: () => void | Promise<void>;
};

export const useAsyncFetch = <T>(
  key: string,
  handler: () => Promise<T>,
  options: AsyncDataOptions<T> & AsyncFetchOptions<T>
) => {
  const asyncData = useAsyncData(
    key,
    async () => {
      await options.onStart?.();
      try {
        const data = await handler();
        await options.onSuccess?.(data);
        return data;
      } catch (error) {
        await options.onError?.(error);
        throw error;
      } finally {
        await options.onFinally?.();
      }
    },
    options
  );

  return {
    ...asyncData,
  };
};

さらに更新系のAPIリクエストにも対応してみる

useAsyncDataはおそらく非同期処理でデータ取得することを目的に提供されているが、statusとして非同期関数の実行状態を管理してくれるのはとても利便性が高い。

なので、immediateはデフォルトtrueのところをfalseに設定して、即時実行されないようにしてあとは同じように実装する。

type AsyncExecOptions<T> = {
  onStart?: () => void | Promise<void>;
  onSuccess?: (data: T) => void | Promise<void>;
  onError?: (error: unknown) => void | Promise<void>;
  onFinally?: () => void | Promise<void>;
};

export const useAsyncExec = <T>(
  handler: () => Promise<T>,
  options: AsyncExecOptions<T>
) => {
  const asyncData = useAsyncData(
    async () => {
      await options.onStart?.();
      try {
        const data = await handler();
        await options.onSuccess?.(data);
        return data;
      } catch (error) {
        await options.onError?.(error);
        throw error;
      } finally {
        await options.onFinally?.();
      }
    },
    { immediate: false }
  );

  return {
    ...asyncData,
  };
};

まとめ

Nuxt3から提供されているuseAsyncDataを使って、よりAPIリクエストを扱いやすくしてみた。もしかするとaxiosとかfetch APIを組み合わせてより特化させても面白いかもしれない。またIndexedDBなども非同期処理でデータ取得・更新を行うようになっているのでそちらでも使えるようにできると思った。