🌐

axios 限定だと思ってたあの機能も aspida/fetch なら使えるよ!

2022/12/06に公開

はじめに

みなさんは aspida を使って型安全な開発してますか?

aspida を使って開発していると、

  • クエリパラメータの配列に[]を付ける/付けない等の設定
  • 400番台などのレスポンスをエラーとして throw する
  • baseURL の設定

をする必要があるから、axios を導入しないといけないなー

しぶしぶ axios を導入することがあると思います。

実は、 aspida と fetch を組み合わせて利用する時には、これらの設定を axios なしで利用できるんです。

aspida が補ってくれる機能

aspida は次のように複数のライブラリ(標準含む)に対応していて、

しかも上記の機能は axios 固有の機能とオーバーラップしているので、aspida 全体の README や解説記事だけを見ていて、それぞれのライブラリの README を見ないと理解しづらいところがありますが、

実は、fetch 用インターフェースは、axios にあって fetch には欠けている機能をいくつか補ってくれています。

こんな感じで設定する
import qs from "qs";

const apiClient = api(
  aspida(
    (...args) => fetch(...args), // fetch 関数をわたす
    {
      baseURL: "/api",
      throwHttpErrors: true,
      paramsSerializer: (s) => qs.stringify(s, { arrayFormat: "brackets" }),
      // fetch の通常のオプションについても、デフォルト値を設定できる
      mode: "cors",
    }
  )
);

https://github.com/aspida/aspida/tree/main/packages/aspida-fetch

「fetch 関数」としてわざわざ (...args) => fetch(...args) を渡す理由

fetch 関数をわたすときに fetch ではなく (...args) => fetch(...args) を渡しているのには理由があります。

MSW (Mock Service Worker) と Next.js を組み合わせているときに出くわしたことがありますが、「モックサーバーが立ち上がる前にリクエストが飛んでしまいエラーが出る」ことがあります。

https://github.com/mswjs/msw/blob/78c7d7eee84b961eb29b2591bd78f81e0c48deef/src/utils/deferNetworkRequestsUntil.ts

なぜなら、MSW は fetch を、「モックサーバーが立ち上がるまでリクエストを投げるのを遅らせる」バージョンの関数に差し替えるからです。
apiClient は、この「 apiClient = api(aspida(...)) を実行した時点での fetch 」をずっと使い続けてしまい、 初期化の順番によっては、差し替え後のものではなく 古い関数の方を使い続けてしまいます

parameterSerializer

パラメータのオブジェクトを文字列に変換する関数を指定することが出来ます。

今回はパラメータの文字列化に qs というライブラリの stringify 関数を利用しています。

主な用途は、 配列の形式の調整だと思います。 これは arrayFormat オプションによって変更可能です。サーバーサイドエンジニアと話し合って、サーバー側が対応している形式に合わせましょう。

ドキュメントから引用
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' })
// 'a=b,c'

▼ ドキュメントはこちら。

https://github.com/ljharb/qs#stringifying

throwHttpErrors

fetch は、通常であれば400番台などのエラーレスポンスをPromise の「成功側」で処理し、「例外側」としては扱ってくれません。 しかし、 aspida/fetch で throwHttpErrors オプションを指定すると、下のコード例のように HTTPError クラスのオブジェクトとして「例外側」として扱えるようになります。

import aspida, { HTTPError } from "@aspida/fetch";

const errorResponseToMsg = (response: Response) => {
  return [
    "HTTP Error",
    `status: ${response.status} ${response.statusText}`,
  ].join("\n");
};

// api 呼び出し
.catch((error) => {
  if (error instanceof HTTPError) {
    window.alert(errorResponseToMsg(error.response));
  } else {
    window.alert(JSON.stringify(error));
  }
});

baseURL

これは言わずもがなですね。

その他設定

また、 fetch 関数にわたすオプションも、予め設定しておくことが出来ます。その apiClient オブジェクトを使ったリクエストには、設定したオプションが初期値として設定されることになります。

株式会社ゆめみ

Discussion