はじめに
「モダンブラウザならfetchが標準で使えるはずなのに、古いブラウザだとfetch is not definedで落ちる」。そんな経験はないでしょうか。Fetch API自体はほとんどのブラウザに実装済みですが、対応表を細かく見ていくと、古いバージョンのSafariやAndroid WebViewなど、まだ穴が残っています。かといって、この対応のためだけにaxiosのような重量級のHTTPクライアントを導入するのも大げさに感じます。
Fetch(パッケージ名はwhatwg-fetch)は、この隙間を埋めるためだけに作られたpolyfillです。GitHub社が開発・公開しており、ブラウザにグローバルなfetch・Headers・Request・Responseを追加し、Fetch APIの仕様(WHATWG Fetch Standard)に沿った挙動を実装します。追加するAPIは標準そのものなので、覚え直す学習コストはほぼゼロです。
とはいえ、説明より動かした方が早いと思います。polyfillを読み込んだ状態でfetch()がどう動くかを、実際にリクエストを送って確認できるサンプルを用意しました。先に挙動を見たい方はこちらからどうぞ。
Fetchとは
Fetchは、GitHub社が開発するwindow.fetchのpolyfillです。npmではwhatwg-fetchという名前で公開されており、GitHubリポジトリ名はfetch(現在はJakeChampion/fetchに移管)です。XMLHttpRequestをラップして、Fetch API仕様に定義されたfetch()関数・Headers・Request・Responseの各クラスをグローバルスコープに追加します。
fetchが未実装のブラウザで使うだけでなく、「本番用にはネイティブのfetchを使いつつ、テスト環境ではpolyfillの挙動に揃えたい」といった用途でも使われています。
主な特徴
- 仕様準拠のpolyfill - 独自APIではなく、WHATWG Fetch Standardに定義された
fetch/Headers/Request/Responseをそのまま実装する - XMLHttpRequestベース - 内部では
XMLHttpRequestを使ってリクエストを行うため、追加の依存なしに動作する - 既存コードを変更しない - polyfillを読み込むだけで、標準の
fetch()呼び出しコードがそのまま古いブラウザでも動く - 軽量 - HTTPクライアントとしての機能追加は行わず、あくまで「標準APIの補完」に徹している
- 長期メンテナンス - GitHub社のプロダクトで実際に使われており、継続的にリリースされている
インストール
npmでインストールする場合です。
npm install whatwg-fetch
エントリーポイントで読み込むと、グローバルにfetchが追加されます。
import 'whatwg-fetch'
CDN経由でそのままscriptタグから読み込むことも可能です。
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3/dist/fetch.umd.js"></script>
Fetchのサンプルを動かす
以下は、whatwg-fetchを読み込んだうえでfetch()を呼び出し、レスポンスをJSONとして受け取るサンプルです。ボタンを押すとリクエストが実行され、Responseオブジェクトのok・status・json()の挙動をその場で確認できます。
要点だけを抜き出すと、次のようになります。
import 'whatwg-fetch'
async function load() {
const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
if (!res.ok) {
throw new Error(`HTTP error: ${res.status}`)
}
const data = await res.json()
return data
}
実際に動かせるものが下です。ボタンを押すとリクエストが送られ、結果が画面に表示されます。
ポイントは、fetch()の返り値がResponseオブジェクトであることです。res.okはstatusが200番台かどうかの真偽値、res.json()はレスポンスボディをJSONとしてパースするPromiseを返します。リクエストURLを別のエンドポイント(例えば存在しないパスなど)に書き換えると、res.okがfalseになり、throwしたエラーメッセージが表示に反映されます。
基本的な使い方
最もシンプルな形は、GETリクエストを送ってJSONを受け取るパターンです。
import 'whatwg-fetch'
fetch('/api/users')
.then((res) => res.json())
.then((users) => console.log(users))
async/awaitを使うと、より見通しよく書けます。
import 'whatwg-fetch'
async function getUsers() {
const res = await fetch('/api/users')
const users = await res.json()
return users
}
実践的なユースケース
Headersでリクエストヘッダーを組み立てる
Headersクラスを使うと、リクエストヘッダーをオブジェクトのように扱えます。Content-Typeの指定や認証トークンの付与など、リクエストごとに異なるヘッダーを組み立てたいときに使います。
headers.append()は同じキーを複数回呼ぶと値を追記し、headers.set()は上書きします。トークンの入力値を変えると、Authorizationヘッダーの中身がその場で組み変わるのが分かります。実際の通信では、このheadersをfetch(url, { headers })のように第2引数のoptionsに渡します。
Requestオブジェクトを再利用する
同じ設定のリクエストを複数箇所から発行したい場合は、Requestオブジェクトをあらかじめ作っておくと、呼び出し側のコードが簡潔になります。設定(URL・メソッド・ヘッダー)を一箇所にまとめられるのが利点です。
ここでのポイントはrequest.clone()です。Requestオブジェクトのボディは一度読み取ると再利用できないため、同じrequestを使い回してボタンを何度も押したい場合はclone()してからfetch()に渡します。clone()を外すと、2回目以降のクリックでエラーになる挙動も確認できます。
まとめ
Fetchは、Fetch API仕様に忠実なfetch/Headers/Request/Responseのpolyfillです。独自のAPIを覚える必要がなく、import 'whatwg-fetch'を1行足すだけで、対象ブラウザの対応状況を気にせず標準のfetch()コードをそのまま使えるようになります。axiosのような高機能なHTTPクライアントを導入するほどではないけれど、古いブラウザのサポートは切りたくない、という場面で選択肢に入れてみてください。
