概要
Fetch APIは、ネットワークリクエストを行うためのブラウザ標準のインターフェースである。
従来の XMLHttpRequest と比較して、簡潔なAPIを提供しており、Promiseベースの設計により非同期処理の記述が容易になっている。
fetch() 関数は、指定したURLへHTTPリクエストを送信してResponseオブジェクトに解決されるPromiseを返す。
GETリクエストによるデータ取得からPOSTリクエストによるデータ送信まで、あらゆるHTTP通信をサポートしている。
Fetch APIの使用時において、重要な特性として、HTTPエラー (404や500等) が発生してもPromiseはrejectされないことが挙げられる。
Promiseがrejectされるのはネットワークエラーが発生した場合のみであり、HTTPエラーの検出には response.ok プロパティを明示的にチェックする必要がある。
リクエストのキャンセルには AbortController を使用する。
AbortSignal.timeout() による簡易タイムアウト設定、AbortSignal.any() による複数シグナルの組み合わせも活用できる。
Reactの useEffect フック内でFetch APIを使用する場合、コンポーネントのアンマウント時にリクエストをキャンセルするクリーンアップ処理を実装することにより、
メモリリークおよびレースコンディションを防止できる。
Fetch APIは全ての最新のWebブラウザで対応しているが、Internet Explorerには対応していない。
fetch()の基本
基本構文
fetch() 関数の基本的な構文を以下に示す。
fetch(url, options)
| 引数 | 説明 |
|---|---|
url |
リクエスト先のURLを、文字列 または URLオブジェクトで指定する。 |
options |
オプションオブジェクト (省略可能) |
下表に、options で指定可能なパラメータを示す。
| パラメータ | 説明 |
|---|---|
method
|
|
headers
|
|
body
|
|
credentials
|
|
mode
|
|
redirect
|
|
signal |
リクエストをキャンセルするための AbortSignal オブジェクトを指定する。
|
Responseオブジェクト
fetch() が返すPromiseは、Response オブジェクトに解決される。
Response オブジェクトの主要なプロパティを以下に示す。
| プロパティ | 説明 |
|---|---|
| ok | ステータスコードが200〜299の範囲にあるかどうかを示すboolean値である。 HTTPエラーの検出に使用する。 |
| status | HTTPステータスコードを示す数値である。 (例: 200, 404, 500) |
| statusText | ステータスに対応するテキストを示す文字列である。 (例: 'OK', 'Not Found')
|
| headers | レスポンスヘッダを含む Headers オブジェクトである。
|
| url | リダイレクト後の最終的なURLを示す文字列である。 |
| redirected | リダイレクトが発生したかどうかを示すboolean値である。 |
レスポンスボディの読み取り
Response オブジェクトは、ボディを読み取るための複数のメソッドを提供している。
いずれのメソッドもPromiseを返す。
| メソッド | 戻り値 | 用途 |
|---|---|---|
response.json() |
JavaScriptオブジェクト | JSONレスポンスの読み取り |
response.text() |
文字列 (UTF-8) | テキストレスポンスの読み取り |
response.blob() |
Blobオブジェクト | 画像・ファイル等のバイナリデータの読み取り |
response.arrayBuffer() |
ArrayBuffer | 低レベルのバイナリデータの読み取り |
response.formData() |
FormDataオブジェクト | フォームデータの読み取り |
※注意
レスポンスボディは1度だけ読み取り可能である。
同じレスポンスボディを複数回読み取る必要がある場合は、response.clone() でレスポンスを複製する。
GETリクエスト
基本的なGETリクエスト
基本的なGETリクエストの例を以下に示す。
const response = await fetch('https://api.example.com/users')
const data = await response.json()
console.log(data)
fetch() はPromiseを返すため、await を使用することにより、Responseオブジェクトを取得できる。
続いて response.json() を await することにより、JSONをJavaScriptオブジェクトに変換できる。
クエリパラメータの付与
クエリパラメータを付与する場合は、URLSearchParams を使用することにより、安全にエンコードできる。
const params = new URLSearchParams({
page: '1',
limit: '10',
search: 'javascript'
})
const response = await fetch(`https://api.example.com/users?${params}`)
const data = await response.json()
URLSearchParams を使用することにより、特殊文字や日本語等を含む値も自動的にパーセントエンコードされる。
文字列連結によるURL構築は特殊文字を含む値でエラーが発生する可能性があるため、URLSearchParams の使用を推奨する。
ヘッダの設定
認証トークンや Accept ヘッダ等を設定する例を以下に示す。
const response = await fetch('https://api.example.com/users', {
headers: {
'Authorization': 'Bearer token123',
'Accept': 'application/json'
}
})
const data = await response.json()
headers オプションにはオブジェクトリテラルを指定できる。
より複雑なヘッダ操作が必要な場合は、Headers オブジェクトを使用することも可能である。
POSTリクエスト
JSONデータの送信
JSONデータをPOSTリクエストで送信する例を以下に示す。
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'John', email: 'john@example.com' })
})
const data = await response.json()
JSONデータを送信する場合は、以下に示す事柄に注意する。
methodに'POST'を指定する。headersに'Content-Type': 'application/json'を設定する。bodyには、JavaScriptオブジェクトをJSON.stringify()で文字列に変換したものを指定する。
FormDataの送信
ファイルアップロード等、マルチパートフォームデータを送信する例を以下に示す。
const formData = new FormData()
formData.append('name', 'John')
formData.append('file', fileInput.files[0])
const response = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
// Content-Typeは設定しない
// Webブラウザが自動的に設定する
})
const data = await response.json()
FormData を送信する場合は、Content-Type ヘッダを手動で設定しない。
Webブラウザが自動的に multipart/form-data を設定して、適切なバウンダリ文字列も付加する。
手動で Content-Type を設定するとバウンダリが欠落し、サーバ側でのパースに失敗する。
エラーハンドリング
HTTPエラーの処理
Fetch APIの重要な特性として、HTTPエラー (404や500等) が発生してもPromiseはrejectされない。
この場合、Promiseは正常に解決されるが、response.ok が false になる。
response.ok を使用したHTTPエラーの処理例を以下に示す。
const response = await fetch('https://api.example.com/users')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
response.ok はステータスコードが200〜299の場合に true となる。
HTTPエラーを適切に処理するには、このチェックを明示的に定義する必要がある。
Promiseがrejectされるのは、サーバに到達できない場合等のネットワークエラーが発生したときのみである。
この場合、TypeError がスローされる。
try {
const response = await fetch('https://api.example.com/users')
const data = await response.json()
}
catch (error) {
if (error instanceof TypeError) {
console.error('ネットワークエラー:', error.message)
}
}
ネットワークエラーの原因には、インターネット接続の切断、DNSエラー、CORS違反等が含まれる。
包括的なエラーハンドリングパターン
HTTPエラーとネットワークエラーの両方を処理する包括的なパターンを以下に示す。
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return await response.json()
}
catch (error) {
if (error instanceof TypeError) {
console.error('ネットワークエラー:', error)
}
else if (error.name === 'AbortError') {
console.error('リクエストがキャンセルされた')
}
else {
console.error('エラー:', error)
}
throw error
}
}
このパターンでは、エラーの種類に応じて以下の分類で処理を行う。
| エラーの種類 | 説明 |
|---|---|
| TypeError | ネットワークエラー (接続失敗等) |
| error.name === 'AbortError' | AbortController によるリクエストのキャンセル
|
| それ以外 | HTTPエラー等のその他のエラー |
AbortControllerによるリクエストキャンセル
基本的な使い方
AbortController を使用することにより、進行中のリクエストをキャンセルできる。
キャンセル時には AbortError という名前の DOMException がスローされる。
const controller = new AbortController()
// 5秒後にキャンセルする
setTimeout(() => controller.abort(), 5000)
try {
const response = await fetch('https://api.example.com/users', {
signal: controller.signal
})
const data = await response.json()
}
catch (error) {
if (error.name === 'AbortError') {
console.log('リクエストがキャンセルされた')
}
}
AbortController の signal プロパティを fetch() の signal オプションに渡すことにより、
controller.abort() を呼び出したタイミングでリクエストがキャンセルされる。
Webブラウザの対応状況: Chrome 66以降、Firefox 57以降、Safari 11以降
AbortSignal.timeout
AbortSignal.timeout() は、指定したミリ秒後に自動的にタイムアウトするシグナルを生成するスタティックメソッドである。
AbortController を手動で作成する必要がなく、タイムアウト処理を簡潔に記述できる。
const response = await fetch('https://api.example.com/users', {
signal: AbortSignal.timeout(5000)
})
タイムアウト発生時には AbortError ではなく、TimeoutError という名前の DOMException がスローされる。
これにより、ユーザによる明示的なキャンセル (AbortError) と タイムアウト (TimeoutError) を区別して処理できる。
Webブラウザの対応状況: Chrome 94以降、Firefox 103以降、Safari 16.4以降、Edge 94以降
AbortSignal.any
AbortSignal.any() は、複数のシグナルを受け取り、いずれかが中断されたときに中断する新しいシグナルを生成するスタティックメソッドである。
タイムアウトとユーザによるキャンセルを組み合わせる場合等に有用である。
const controller = new AbortController()
const response = await fetch('https://api.example.com/users', {
signal: AbortSignal.any([
AbortSignal.timeout(5000),
controller.signal
])
})
上記の例では、5秒のタイムアウトが発生 または controller.abort() が呼び出されるかのいずれか早い方でリクエストがキャンセルされる。
Webブラウザの対応状況: Chrome 123以降、Firefox 121以降、Safari 17.4以降、Edge 123以降
Reactでの使用パターン
useEffectでのデータ取得
Reactコンポーネントの useEffect フック内でFetch APIを使用する際の推奨パターンを以下に示す。
import { useState, useEffect } from 'react'
function UserList() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const controller = new AbortController()
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch('https://api.example.com/users', {
signal: controller.signal
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const result = await response.json()
setData(result)
setError(null)
}
catch (error) {
if (error.name !== 'AbortError') {
setError(error.message)
}
}
finally {
setLoading(false)
}
}
fetchData()
return () => controller.abort()
}, [])
if (loading) return <div>読み込み中...</div>
if (error) return <div>エラー: {error}</div>
return <pre>{JSON.stringify(data, null, 2)}</pre>
}
useEffect のクリーンアップ関数で controller.abort() を呼び出すことには以下の重要な意義がある。
- メモリリークの防止
- コンポーネントがアンマウントされた後に非同期処理が完了しても、状態の更新が実行されなくなる。
- 不要なリクエストのキャンセル
- コンポーネントがアンマウントされた時点で進行中のリクエストをキャンセルする。
- レースコンディションの防止
- 依存配列の値が変化して再レンダリングが発生した場合、前のリクエストをキャンセルしてから新しいリクエストを開始する。
AbortError はコンポーネントの正常なライフサイクルの一部であるため、エラーとして扱わないよう error.name !== 'AbortError' でフィルタリングしている。
関連情報