axios 限定だと思ってたあの機能も aspida/fetch なら使えるよ!
はじめに
みなさんは 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",
}
)
);
「fetch 関数」としてわざわざ (...args) => fetch(...args) を渡す理由
fetch 関数をわたすときに fetch
ではなく (...args) => fetch(...args)
を渡しているのには理由があります。
MSW (Mock Service Worker) と Next.js を組み合わせているときに出くわしたことがありますが、「モックサーバーが立ち上がる前にリクエストが飛んでしまいエラーが出る」ことがあります。
なぜなら、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'
▼ ドキュメントはこちら。
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