ホーム

Next.jsでカスタムしたfetch関数を使ってapiを呼び出す

作成日: 2023-7-11

更新日: 2023-10-25

Next.jsでカスタムしたfetch関数を使ってapiを呼び出す

この記事を読んだ後に理解できること


  • 非同期処理の概要
  • JavaScriptにおける非同期処理について
  • Next.jsでfetchを使う場合の便利な実装方法


前置き


この記事では、Next.jsでカスタムしたfetch関数を使ってapiを呼び出す方法について解説しています。
前提条件として、非同期処理についても理解しておく必要がありますので、そちらについても解説しています。

なぜfetch関数なのか


データフェッチングといえば、axios等が主流です。

しかし、Next.js13のApp Routerでは、キャッシュ制御等の一部機能(revalidate)がfetch関数に依存しているため、その部分については、fetch関数を使う方が無難な選択です。

もちろん、fetch関数を使わなくてもキャッシュの制御等はできます。

キャッシュ制御等を行う必要がある場合でかつ簡単に行いたい場合は、fetch関数を使うことが無難な選択となります。

非同期処理とは


非同期処理とは、裏側で行われる処理のことを指します。

処理時間が長くなる場合、非同期処理で実行させるケースが多いです。

例えば、フロントからバックエンド(API)に対して、リクエストを行う通信が発生するケースなどは非同期処理で実装する代表的な例です。

その他の非同期処理で実装するケースとしては、ファイルの読み書きなどが挙げられます。

JavaScriptにおける非同期処理について


JavaScript・TypeScriptにおける非同期処理は、ノンブロッキングな処理であることを指します。

ノンブロッキングとは、時間のかかる処理の完了を待たずにプログラムを続行する処理のことです。

例えば、APIに対してリクエストを送ってレスポンスを取得する処理は、JavaScriptではノンブロッキングな処理として扱うことが推奨されます。

JavaScriptでは、シングルスレッドというモデルが採用されています。これは、プログラムが1箇所でのみ実行されるモデルであり、複数箇所で同時に実行されることはありません。

そのため、先程のAPIリクエストのような処理をブロッキングな処理で実装してしまうとプログラムの実行が止まってしまい、弊害が出る可能性があります。

Promiseについて


Promiseは分かりやすい形で非同期処理を扱うことができる機能です。

Promiseオブジェクトを返す関数は、.thenメソッドを持っています。

このthenメソッドの引数で非同期処理成功時のコールバック関数を受け取ることができます。

例えば、fetch()はAPIリクエストをPromiseオブジェクトを返す関数です。

const response = fetch('https://example.com/');

response.then((data) => console.log(data));


もしPromiseがない場合、コールバック関数を使う必要があります。
※fetch()はコールバック関数を引数にセットすることはできません。あくまで例です。

fetch("https://example.com", (err, data) => {
  console.log(data);
})


この程度であれば、コールバック関数でも対応できるのですが、非同期処理は複雑になるケースが多いので、Promiseオブジェクトを使った処理で書いた方が可読性が高くなります。

非同期処理が失敗した場合の処理については、catch()メソッドでコールバック関数を設定できます。

const response = fetch('https://example.com/');

response.then((data) => console.log(data));

response.catch((err) => console.error(err));


Promiseの基本的な使い方は以上になります。

async awaitについて


async,await関数を使うとさらに簡潔に非同期処理の実装ができるようになります。

asyncを使う関数は戻り値が必ずPromiseにになります。

async function getString() {
  return 'string'
}

// asyncを使わない場合は、以下
function getString() {
  return Promise.resolve('string');
}


この関数の戻り値は、どちらもPromise<string>になります。
async構文を使った方がシンプルで読みやすいです。

awaitについては、先程のfetch()を例に解説します。

概要について解説しておくとawaitはasyncの中で使うことができる構文です。

awaitを使うことでPromiseの結果を得ることができます。

fetch()は、取得したデータに対してjson()を実施することでJSONデータを取得することができます。
ただし、json()の戻り値は、Promiseオブジェクトであるため、.thenで繋ぐ必要があります。

const response = fetch('https://example.com/');

const json = response.then((data) => data.json()));

json.then((data) => console.log(data);

response.catch((err) => console.error(err));


awaitを使うと以下のように実装できます。

async funtion fetcher() = {
  const response = await fetch('https://example.com/');
  // awaitを使わない場合、.thenで結果を取得する必要がある
  const data = await response.json();
  console.log(data);
}


かなりスッキリしました。

Next.jsで使うためにfetch関数をカスタムする


fetch関数を使う上での注意点


  • 一部エラーがcatchできない
  • json()を使う必要がある


axiosでは、200以外のレスポンhttpスステータスはcatchで取得することができますが、fetchはthenに入ってしまいます。
また、レスポンスに対しては、json()を使う必要があります。

Next.jsで使うためにカスタムしたfetch関数の実装


先に全体の概要です。

// fetcher.ts
/**
 * APIリクエスト失敗時のエラーステータスを呼び出し側で取得するため、Errorクラスをextendsする
 */
class CustomError extends Error {

  status: number

  constructor(message: string, status: number) {
    super(message)
    this.status = status
  }
}

export async function fetcher<JSON = any>(
  input: RequestInfo,
  init?: RequestInit & {next?: {revalidate: number}}
  ): Promise<JSON>{
    const res = await fetch(input, init)
  
    if (!res.ok) {
      try {
        const err: {message: string} = await res.json()
        throw new CustomError(err.message, res.status)
      } catch {
        throw new CustomError('レスポンス解析に失敗しました', res.status)
      }
    }
  
    return await res.json()
  }


エラーをcatchする


注意点でも解説しましたが、fetch関数はエラー(4xxや5xx)がcatchに入りません。

不便なので、ラップしたfetcher関数を使うときは、catchにエラーが入るようにします。
エラーメッセージだけでなく、ステータスも投げたいので、Errorクラスをextendsして、ステータスも参照できるようにします。

/**
 * APIリクエスト失敗時のエラーステータスを呼び出し側で取得するため、Errorクラスをextendsする
 */
class CustomError extends Error {

  status: number

  constructor(message: string, status: number) {
    super(message)
    this.status = status
  }
}


revalidateオプションを設定できるようにする


Next.jsのAppRouterでrevalidate(ISR)を使う場合は、以下のようにfetch関数のオプションとして、next.revalidaeを設定する必要があります。

fetch('https://...', { next: { revalidate: 60 } })


fetch関数のoptionの型であるRequstInitに&を使ってnext.revalidateの型を追加します。

  input: RequestInfo,
  init?: RequestInit & {next?: {revalidate: number}}


概ねaxiosのようにresponseやエラーハンドリングが可能となります。

カスタムしたfetch関数でapiを呼び出す


カスタムしたfetch関数は以下のように使うことができます。

// 例につきimport等は一部省略
import {fetcher} from '@/libs/fetcher.ts'

const [data, setData] = useState<{message: string}[]>([]);

useEffect(() => {
 const fetchData = async () => {
   try {
    const response = await fetcher<{message: string}>(
         'https://example.com', 
         {
            method: "GET", 
            next: {revalidate: 60}
         })
    setData(response);
   } catch (err) {
     console.error(err.message)
   }
 };
  
 fetchData();
}, []);


参考書籍

こちらの記事は以下の書籍を参考にしている箇所があります。