はい、承知いたしました。JavaScriptのFetch APIの基本的な使い方について、詳細な説明を含む約5000語の記事を記述します。記事本文を直接表示します。
今すぐ使える!JavaScript fetch APIの基本的な使い方徹底解説
はじめに:なぜ今、Fetch APIなのか?
Web開発において、JavaScriptを使って外部リソース(サーバー上のデータやAPIなど)を取得したり、データを送信したりする処理は避けて通れません。古くからこの役割を担ってきたのが XMLHttpRequest
(XHR) という組み込みオブジェクトです。しかし、XHRは設定がやや複雑で、非同期処理の管理もコールバック地獄になりがちという課題がありました。
そこで登場したのが Fetch API です。Fetch APIは、ネットワークリクエストを行うためのモダンで強力なインターフェースを提供します。Promiseベースであるため、非同期処理をよりシンプルかつ直感的に記述でき、コードの可読性と保守性を飛躍的に向上させます。現在ではほとんどのモダンブラウザでサポートされており、Web開発の現場で最も一般的に利用される非同期通信手段となっています。
この記事では、Fetch APIの基本的な使い方から、GET/POSTリクエストの送信、エラーハンドリング、そして現代的な非同期処理である async/await
との組み合わせ方まで、実践的なコード例を交えながら徹底的に解説します。この記事を読めば、あなたもすぐにFetch APIを使いこなせるようになるでしょう。
Part 1: Fetch APIとは何か? XMLHttpRequestとの比較
Fetch APIは、HTTPリクエストとレスポンスを扱うための強力で柔軟な仕組みです。XMLHttpRequest (XHR) が提供していた機能の多くをカバーしつつ、より優れた設計思想に基づいています。
XMLHttpRequest (XHR) の課題
XHRは長年にわたり非同期通信のデファクトスタンダードとして利用されてきましたが、いくつかの課題を抱えていました。
- コールバックベースの非同期処理: XHRはイベントハンドラー(
onload
,onerror
,onreadystatechange
など)に処理を記述するコールバック方式です。複数の非同期処理が連鎖する場合や、複雑なロジックを記述しようとすると、コードが深くネストされ、いわゆる「コールバック地獄 (Callback Hell)」に陥りやすく、コードの理解やメンテナンスが難しくなります。 - 設定の複雑さ: リクエストの準備から送信、状態変化の監視、エラー処理まで、多くのメソッドやプロパティを操作する必要があります。
- 非同期処理の表現力: XHRは低レベルなAPIであり、リクエスト/レスポンスのストリーム処理や、より高度なHTTP機能(Service Worker連携など)の表現が難しい側面がありました。
- JSONの扱いの不便さ: レスポンスボディをJSONとして扱う場合、responseTextを取得してから
JSON.parse()
を手動で行う必要がありました。
Fetch APIの利点
Fetch APIは、これらのXHRの課題を解決するために設計されました。
- Promiseベース: Fetch APIの根幹はPromiseオブジェクトです。Promiseを使うことで、非同期処理の成功・失敗を
.then()
や.catch()
で明確に記述でき、処理の連鎖もフラットに書けます。これにより、コールバック地獄を回避し、コードの可読性が向上します。さらに、後述するasync/await
と組み合わせることで、非同期処理をまるで同期処理のように直感的に書くことができます。 - シンプルで統一されたインターフェース: リクエストの送信は
fetch()
関数一つで行います。設定はオプションオブジェクトとして渡すため、引数が煩雑になりません。レスポンスの処理も、Response
オブジェクトのメソッド(json()
,text()
など)を呼び出すだけで、データ形式に応じたパースとPromiseの解決が行われます。 - Web標準との整合性: Fetch APIは、Request、Response、Headers、BodyといったHTTPの概念を反映したオブジェクト(インターフェース)を提供します。これらのインターフェースはService WorkerやCache APIといった他のモダンなWeb APIとも共通しており、Webプラットフォーム全体での整合性が高まっています。
- ストリーム対応: レスポンスボディをストリームとして扱うことが可能です(ただし、この記事の基本的な使い方では深く触れません)。
Fetch APIは、そのシンプルさ、Promiseベースの非同期処理、そして標準との整合性から、現代のWeb開発において非同期通信の第一選択肢となっています。
Part 2: Fetch APIの基本の「キ」:fetch()
関数
Fetch APIの核となるのが fetch()
関数です。この関数は、ネットワークリソースを取得するためのリクエストを開始し、そのリクエストの結果を表現する Promise を返します。
fetch()
関数の基本的な構文は以下の通りです。
javascript
fetch(resource, options)
resource
(必須): 取得したいリソースのURLを指定します。文字列またはRequest
オブジェクトを指定できます。この記事では主に文字列URLを使います。options
(省略可能): リクエストに関する設定を指定するオブジェクトです。HTTPメソッド(GET, POSTなど)、ヘッダー、ボディ、認証情報などを指定します。省略した場合、デフォルトではHTTPメソッドはGET
となり、その他のオプションは基本的な設定が適用されます。
fetch()
関数は Promise を返します。この Promise は、リクエストに対するレスポンスヘッダーが正常に取得できた時点で解決(resolve)されます。解決されたPromiseの値は Response
オブジェクトです。ネットワークエラーなど、リクエストが完了する前に問題が発生した場合は、Promiseは拒否(reject)されます。
最小構成のGETリクエスト
最も基本的な使い方は、URLだけを指定するGETリクエストです。
“`javascript
// 例1: 最小構成のGETリクエスト
fetch(‘https://jsonplaceholder.typicode.com/posts/1’)
.then(response => {
// レスポンスヘッダーが正常に取得できた(HTTPステータスが2xxの範囲でなくても)
console.log(‘Response received:’, response);
// ここでレスポンスボディを読み取る処理を行う
// response.json(), response.text() などが Promise を返す
return response.json(); // レスポンスボディをJSONとして読み取る
})
.then(data => {
// レスポンスボディの読み取り(json()の解決)が成功した場合
console.log(‘Data received:’, data);
})
.catch(error => {
// ネットワークエラーなど、リクエスト自体が失敗した場合
console.error(‘Fetch error:’, error);
});
console.log(‘Fetch request initiated…’); // fetchは非同期なので、これは先に実行される
“`
このコードの実行順序と仕組みを見ていきましょう。
fetch('...')
が呼び出され、HTTPリクエストが開始されます。fetch()
はすぐに Promise オブジェクトを返します。console.log('Fetch request initiated...');
がすぐに実行されます。これは非同期処理の典型的な動作です。- ネットワーク経由でサーバーと通信が行われます。
- サーバーからレスポンスヘッダーが返ってくると、
fetch()
が返した Promise が解決されます。解決された値はResponse
オブジェクトです。 .then(response => { ... })
内のコールバック関数が実行されます。引数response
にはResponse
オブジェクトが入っています。- この最初の
.then()
の中で、response.json()
を呼び出しています。response.json()
は、レスポンスボディをJSONとしてパースし、その結果を解決する新しい Promise を返します。ここで重要なのは、fetch()
が返す Promise が解決されても、レスポンスボディが完全に読み込まれてパースされたわけではないということです。 ボディの読み取りは別途response.json()
,response.text()
などのメソッドで行う必要があり、これらのメソッドも Promise を返します。 - 最初の
.then()
のコールバックは、response.json()
が返した Promise をreturn
しています。Promise チェーンでは、.then()
のコールバックが Promise を返すと、その Promise が解決または拒否されるまで次の.then()
は待機します。 response.json()
が成功裏にレスポンスボディをパースし終えると、その Promise が解決されます。解決された値(この例ではパースされたJSONデータ)が次の.then()
のコールバック関数に渡されます。- 次の
.then(data => { ... })
内のコールバック関数が実行され、引数data
にパースされたJSONデータが入っています。 - リクエスト中にネットワークエラー(例: サーバーに到達できない、クロスオリジンポリシー違反など)が発生した場合、または最初の
.then()
の処理中にエラーが発生した場合、.catch(error => { ... })
内のコールバック関数が実行され、エラー情報が引数error
に渡されます。
Promiseチェーンについて
Fetch APIを理解する上で、Promiseチェーンの概念は非常に重要です。Promiseチェーンは、一連の非同期処理を順番に実行し、それぞれの結果を次の処理に引き渡すための仕組みです。
javascript
asyncOperation1() // Promise を返す
.then(result1 => {
// asyncOperation1 が成功したら実行される
// result1 を使って次の非同期処理を開始する
return asyncOperation2(result1); // 新しい Promise を返す
})
.then(result2 => {
// asyncOperation2 が成功したら実行される
// result2 を使って次の非同期処理を開始する
return asyncOperation3(result2); // 新しい Promise を返す
})
.then(result3 => {
// asyncOperation3 が成功したら実行される
// 最終的な結果を処理する
console.log('All operations finished successfully:', result3);
})
.catch(error => {
// 上記のどの処理でエラーが発生しても捕捉される
console.error('An error occurred:', error);
});
Fetch APIの例に戻ると、fetch()
が返す Promise を最初の .then()
で受け取り、その中でレスポンスボディを読み取るための Promise (response.json()
など) を返しています。この返された Promise が解決されると、次の .then()
が実行され、最終的なデータを受け取って処理するという流れになっています。
このチェーン構造により、非同期処理の順序とデータの流れが非常に明確になり、コールバック地獄のような深いネストが解消されます。
Response
オブジェクト
fetch()
が成功した Promise は Response
オブジェクトを引数として解決されます。この Response
オブジェクトには、レスポンスに関する様々な情報が含まれています。
主要なプロパティとメソッド:
status
: HTTPステータスコード(例: 200, 404, 500など)を示す数値。statusText
: ステータスコードに対応するテキスト(例: “OK”, “Not Found”, “Internal Server Error”など)。ok
: HTTPステータスコードが200
から299
の範囲内であるかを示す真偽値。Fetch APIは、HTTPステータスコードが4xxや5xxであっても、ネットワークエラーでなければPromiseを解決することに注意が必要です。 成功したリクエストかどうかはresponse.ok
をチェックする必要があります。headers
:Headers
オブジェクト。レスポンスヘッダーに関する情報を含みます。url
: リダイレクトなどを考慮した最終的なリクエストURL。json()
: レスポンスボディをJSONとして読み取り、JavaScriptオブジェクトにパースするPromiseを返します。text()
: レスポンスボディをプレーンテキストとして読み取るPromiseを返します。blob()
: レスポンスボディをBlob
オブジェクトとして読み取るPromiseを返します(画像やファイルデータなどに利用)。arrayBuffer()
: レスポンスボディをArrayBuffer
オブジェクトとして読み取るPromiseを返します(バイナリデータなどに利用)。formData()
: レスポンスボディをFormData
オブジェクトとして読み取るPromiseを返します(フォームデータのレスポンスなどに利用)。
これらのボディ読み取りメソッドは、それぞれ Promise を返します。したがって、.then()
の中でこれらのメソッドを呼び出し、そのPromiseを返すことで、次の .then()
で実際に読み取られたデータを受け取ることができます。
``javascript
HTTP error! status: ${response.status}`);
// 例2: レスポンスボディの様々な読み取り方
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => {
if (!response.ok) {
// HTTPエラーレスポンス (404, 500など)
throw new Error(
}
console.log(‘Status:’, response.status);
console.log(‘Status Text:’, response.statusText);
console.log(‘OK:’, response.ok);
console.log(‘Headers:’, response.headers); // Headers オブジェクト
console.log(‘Content-Type:’, response.headers.get(‘Content-Type’)); // 特定のヘッダーを取得
console.log(‘URL:’, response.url);
// Content-Typeに応じて適切なボディ読み取りメソッドを選択するのが一般的
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return response.json(); // JSONとして読み取る
} else {
return response.text(); // それ以外はテキストとして読み取る
}
})
.then(data => {
console.log(‘Parsed Body Data:’, data);
})
.catch(error => {
console.error(‘Error during fetch or processing:’, error);
});
// テキストレスポンスの例 (適当なテキストファイルを想定)
// fetch(‘/path/to/your/textfile.txt’)
// .then(response => {
// if (!response.ok) {
// throw new Error(HTTP error! status: ${response.status}
);
// }
// return response.text(); // テキストとして読み取る
// })
// .then(text => {
// console.log(‘Text content:’, text);
// })
// .catch(error => {
// console.error(‘Error fetching text file:’, error);
// });
“`
この例では、まず response.ok
をチェックしてHTTPエラーを早期に検出しています。エラーの場合は throw new Error()
で次の .catch()
へ処理を移します。Headers
オブジェクトから Content-Type
ヘッダーを取得し、それに応じて response.json()
か response.text()
を呼び分けています。このように、実際のアプリケーションではレスポンスの Content-Type
を見て適切なボディ読み取りメソッドを選ぶことが重要です。
Part 3: GETリクエストをマスターする
最も一般的なHTTPメソッドであるGETリクエストは、サーバーからデータを取得するために使用されます。特別な設定が不要な場合は、Part 2で見たようにURLだけを指定すればOKです。
javascript
// 例3: 基本的なGETリクエスト
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json())
.then(user => {
console.log('User data:', user);
})
.catch(error => {
console.error('Failed to fetch user:', error);
});
クエリパラメータの追加
GETリクエストでサーバーに付加的な情報を渡したい場合(例: 検索条件、ページネーション情報など)、URLにクエリパラメータを追加します。クエリパラメータはURLの末尾に ?key1=value1&key2=value2
の形式で追加します。
JavaScriptでクエリパラメータを扱う際には、URLSearchParams
インターフェースを使うと便利です。これは、URLのクエリ文字列を簡単に作成・操作するための機能です。
“`javascript
// 例4: クエリパラメータ付きGETリクエスト
const baseUrl = ‘https://jsonplaceholder.typicode.com/posts’;
const params = new URLSearchParams({
userId: 1,
_limit: 5 // ユーザーIDが1の投稿を最大5件取得
});
const urlWithParams = ${baseUrl}?${params.toString()}
;
console.log(‘Request URL:’, urlWithParams); // https://jsonplaceholder.typicode.com/posts?userId=1&_limit=5
fetch(urlWithParams)
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
return response.json();
})
.then(posts => {
console.log(‘Posts:’, posts);
})
.catch(error => {
console.error(‘Failed to fetch posts:’, error);
});
“`
URLSearchParams
を使うことで、キーと値のペアをオブジェクトや配列で指定するだけで、適切な形式のクエリ文字列が生成されます。これは、特殊文字のエスケープなども自動で行ってくれるため安全です。手動で文字列結合するよりも推奨される方法です。
Part 4: POSTリクエストでデータを送信する
データをサーバーに送信する場合(例: フォームの送信、新しいリソースの作成など)には、主にPOSTメソッドを使用します。POSTリクエストでは、fetch()
関数の第二引数であるオプションオブジェクトが必須となります。
POSTリクエストの基本的な構造:
javascript
fetch(url, {
method: 'POST', // HTTPメソッドを指定
headers: {
// 送信するデータの形式を示すヘッダーを指定
'Content-Type': 'application/json' // 例: JSON形式
// 他にも必要なヘッダーがあれば追加
},
body: JSON.stringify({ // 送信するデータを指定
title: 'foo',
body: 'bar',
userId: 1
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // サーバーからのレスポンスをJSONとして読む
})
.then(data => {
console.log('New post created:', data);
})
.catch(error => {
console.error('Failed to create post:', error);
});
オプションオブジェクトの主要なプロパティ:
method
: HTTPメソッドを指定します。GET, POST, PUT, DELETE, PATCH, HEADなどがあります。デフォルトはGET
です。大文字で指定するのが慣例です。headers
: リクエストヘッダーを指定するオブジェクトまたはHeaders
オブジェクトです。Content-Type
,Authorization
など、サーバーに伝える必要のある情報を設定します。body
: リクエストボディとして送信するデータを指定します。文字列、Blob
,BufferSource
,FormData
,URLSearchParams
,ReadableStream
など様々な型を指定できます。最も一般的なのは、JSONデータを文字列化したものです。
JSONデータの送信
REST APIなどで最も一般的に使われるデータ形式はJSONです。JavaScriptオブジェクトをJSON形式で送信するには、以下の手順を踏みます。
- 送信したいデータをJavaScriptオブジェクトとして準備します。
- そのオブジェクトを
JSON.stringify()
を使ってJSON形式の文字列に変換します。 - リクエストヘッダーに
'Content-Type': 'application/json'
を設定します。これは、サーバーに対して「送信しているボディのデータ形式はJSONです」と伝えるために非常に重要です。 - 変換したJSON文字列を
body
プロパティに設定します。
“`javascript
// 例5: JSONデータをPOST送信
const newPost = {
title: ‘Fetch APIで送る新しい記事’,
body: ‘これはFetch APIのPOSTリクエストのテストです。’,
userId: 5
};
fetch(‘https://jsonplaceholder.typicode.com/posts’, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json; charset=UTF-8’ // 文字コードも指定することが多い
},
body: JSON.stringify(newPost) // オブジェクトをJSON文字列に変換
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
return response.json();
})
.then(data => {
console.log(‘Server response (new post data):’, data);
// jsonplaceholderは新しいリソースを作成すると、IDを付与して返す
console.log(‘Newly created post ID:’, data.id);
})
.catch(error => {
console.error(‘Error creating new post:’, error);
});
“`
フォームデータの送信
HTMLのフォーム要素からデータを送信する場合など、application/x-www-form-urlencoded
または multipart/form-data
形式でデータを送信したい場合もあります。Fetch APIでは FormData
オブジェクトを使用すると便利です。
FormData
オブジェクトは、キーと値のペアを保持し、HTTPリクエストのボディとして送信するのに適した形式に自動的に変換してくれます。特に multipart/form-data
形式でのファイル送信にも対応しています。
“`javascript
// 例6: FormDataをPOST送信
const form = new FormData();
form.append(‘username’, ‘testuser’);
form.append(‘password’, ‘password123’);
// ファイルを添付する場合 (input type=”file” 要素などから取得)
// const fileInput = document.querySelector(‘input[type=”file”]’);
// form.append(‘profilePicture’, fileInput.files[0]);
// テスト用のエンドポイント (例: RequestBin や他のダミーAPI) を使用してください
// fetch(‘YOUR_FORM_DATA_ENDPOINT’, {
fetch(‘https://httpbin.org/anything’, { // httpbin.org はリクエスト情報をそのまま返すテストサイト
method: ‘POST’,
// FormData を body に設定する場合、Content-Type ヘッダーを自分で設定してはいけません。
// ブラウザが適切な Content-Type (multipart/form-data と境界文字列) を自動で設定します。
// headers: { ‘Content-Type’: ‘…’ } // これをコメントアウトまたは削除
body: form
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
return response.json();
})
.then(data => {
console.log(‘Server response (FormData):’, data);
// httpbin.org の場合、form の内容が response.form プロパティに入っている
console.log(‘Received form data:’, data.form);
})
.catch(error => {
console.error(‘Error sending form data:’, error);
});
“`
FormData
オブジェクトを body
に設定する場合、手動で Content-Type
ヘッダーを設定してはいけません。ブラウザが自動的に適切な Content-Type
ヘッダー(multipart/form-data
と、ボディの各パートを区切るための境界文字列)を生成してくれます。これにより、ファイルアップロードなども正しく処理されます。
その他のデータ形式
body
プロパティには、URLSearchParams
オブジェクトも指定できます。これは application/x-www-form-urlencoded
形式でデータを送信したい場合に便利です。
“`javascript
// 例7: URLSearchParamsをPOST送信 (application/x-www-form-urlencoded)
const params = new URLSearchParams();
params.append(‘name’, ‘Test User’);
params.append(‘email’, ‘[email protected]’);
// テスト用のエンドポイント (例: httpbin.org)
fetch(‘https://httpbin.org/anything’, {
method: ‘POST’,
// URLSearchParams を body に設定する場合、Content-Type は自動で application/x-www-form-urlencoded になります
// 明示的に指定しても良いですが、通常は不要です。
// headers: {
// ‘Content-Type’: ‘application/x-www-form-urlencoded; charset=UTF-8’
// },
body: params
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
return response.json();
})
.then(data => {
console.log(‘Server response (URLSearchParams):’, data);
// httpbin.org の場合、form の内容が response.form プロパティに入っている
console.log(‘Received form data (urlencoded):’, data.form);
})
.catch(error => {
console.error(‘Error sending urlencoded data:’, error);
});
“`
URLSearchParams
を body
に設定した場合も、通常は Content-Type
ヘッダーを明示的に指定する必要はありません。ブラウザが自動的に application/x-www-form-urlencoded
を設定します。
Part 5: エラーハンドリングは必須
非同期通信において、エラーハンドリングは非常に重要です。リクエストが成功するとは限らず、ネットワークの問題やサーバー側のエラーなど、様々な要因で処理が失敗する可能性があります。Fetch APIでは、Promiseの仕組みを利用してエラーを捕捉します。
Fetch APIで発生しうるエラーは、大きく分けて2種類あります。
- ネットワークエラー: リクエストを送信できなかった場合、サーバーに到達できなかった場合、あるいはCORS違反のようなプロトコルレベルのエラーが発生した場合などです。これらのエラーが発生すると、
fetch()
が返した Promise は拒否 (reject) されます。これは.catch()
ブロックで捕捉できます。 - HTTPエラー: サーバーからレスポンスは返ってきたものの、そのステータスコードがエラーを示す場合(404 Not Found, 500 Internal Server Errorなど)です。Fetch APIは、これらのHTTPエラーが発生してもPromiseを拒否しません! レスポンスヘッダーが正常に取得できれば、Promiseは解決 (resolve) されます。したがって、HTTPエラーを検出するには、
Response
オブジェクトのok
プロパティやstatus
プロパティをチェックする必要があります。
.catch()
を使ったネットワークエラーの捕捉
fetch()
関数が返すPromiseが拒否された場合(主にネットワークエラー)、.catch()
メソッドでそのエラーを捕捉できます。
javascript
// 例8: ネットワークエラーの捕捉
fetch('https://invalid-url-likely-to-fail.com/data') // 存在しないドメインなど
.then(response => {
// ネットワークエラーの場合はここに到達しない
console.log('Response received (but might be HTTP error):', response);
return response.json(); // この時点でエラーになる可能性もある (無効なレスポンスボディなど)
})
.then(data => {
// ここはネットワークエラーやパースエラーの場合は到達しない
console.log('Data received:', data);
})
.catch(error => {
// fetch が失敗したネットワークエラー(Promiseがrejectされた場合)
// または、それより前の .then ブロックで発生したエラー(例: response.json() のパースエラー)
console.error('An error occurred:', error);
// error.message などで詳細を確認できることが多い
if (error instanceof TypeError) {
console.error('Looks like a network error or CORS issue:', error.message);
} else {
console.error('An unexpected error occurred:', error);
}
});
.catch()
ブロックは、その手前のPromiseチェーンのどこかで発生したエラー(Promiseが拒否された場合)をまとめて捕捉します。fetch()
自体が返すPromiseが拒否されるのは主にネットワークレベルのエラーです。response.json()
などのボディ読み取りメソッドが返すPromiseが拒否されるのは、レスポンスボディが期待される形式でなかった場合(例: JSONではないレスポンスに対して json()
を呼んだ場合)などです。
response.ok
と response.status
を使ったHTTPエラーの検出
前述の通り、HTTPステータスコードが4xxや5xxの場合でも、fetch()
自体は成功したPromiseを返します。HTTPエラーをエラーとして扱うためには、開発者自身が Response
オブジェクトをチェックし、エラーと判断した場合は明示的にエラーを発生させる必要があります。
“`javascript
// 例9: HTTPエラーの検出と処理
fetch(‘https://jsonplaceholder.typicode.com/posts/9999’) // 存在しないリソースを要求
.then(response => {
console.log(‘Response Status:’, response.status); // 例: 404
console.log(‘Response OK:’, response.ok); // 例: false
if (!response.ok) {
// HTTPステータスコードが 200-299 の範囲外であればエラーとみなす
// Errorオブジェクトを作成し、throw する
throw new Error(`HTTP error! Status: ${response.status}`);
}
// 2xx ステータスの場合は、レスポンスボディを読み進める
return response.json();
})
.then(data => {
// HTTPエラーが発生しなかった場合にのみ到達
console.log(‘Data received:’, data);
})
.catch(error => {
// ネットワークエラーまたは前の .then で throw されたエラーを捕捉
console.error(‘Error:’, error);
console.error(‘Error message:’, error.message); // 例: “HTTP error! Status: 404”
});
“`
このパターンが、Fetch APIを使う上で最も標準的かつ重要なエラーハンドリングの方法です。最初の .then()
ブロックで必ず response.ok
をチェックし、false
の場合はカスタムエラー(または標準の Error
オブジェクト)を作成して throw
します。これにより、HTTPエラーもネットワークエラーと同様に .catch()
ブロックで一元的に処理できるようになります。
エラーメッセージには、response.status
や response.statusText
を含めると、デバッグに役立ちます。
より詳細なエラー処理
HTTPエラーの場合、サーバーがエラーの詳細をレスポンスボディに含めて返すことがあります(例: エラーメッセージやエラーコードをJSONで返す)。そのような場合、response.ok
でエラーを検出した後、レスポンスボディからエラー情報を読み取ってからエラーを throw
する、というパターンも考えられます。
``javascript
HTTP error! Status: ${response.status}, Details: ${JSON.stringify(errorBody)}`);
// 例10: レスポンスボディからエラー情報を取得してthrowする
fetch('https://your-api.com/resource') // 例: エラー時には { message: "Invalid input", code: 123 } を返すAPI
.then(response => {
if (!response.ok) {
// HTTPエラーの場合は、ボディを読み取って詳細を確認する
return response.json().then(errorBody => {
// ボディを読み取った後、Promiseをrejectする
// Promise.reject() を使うか、throw new Error() を使うか、どちらでも良いが Promise.reject() は非推奨
// ここで throw すると、次の catch ブロックに渡る
throw new Error(
});
}
// OKの場合は、ボディを読み進める
return response.json();
})
.then(data => {
console.log(‘Data received:’, data);
})
.catch(error => {
console.error(‘Request failed:’, error);
console.error(‘Error details:’, error.message);
});
// 注意: response.json() は一度しか呼び出せません。
// response.text() なども同様に、ボディ読み取りメソッドは一度だけ使用可能です。
“`
このパターンでは、HTTPエラー発生時にも response.json()
(または text()
, blob()
など) を呼び出しています。response.json()
も Promise を返すため、ここでも .then()
を使ってパースされたエラーボディを取得し、その情報を含めた新しいエラーを throw
しています。これにより、.catch()
ブロックでHTTPステータスだけでなく、サーバーが返した具体的なエラー内容も確認できるようになります。
Part 6: 他のHTTPメソッド (PUT, DELETEなど)
GETとPOST以外にも、PUTやDELETEといったHTTPメソッドは、既存のリソースを更新したり削除したりするために使用されます。Fetch APIでこれらのメソッドを使う場合も、POSTリクエストと同様にオプションオブジェクトで method
プロパティを指定します。
- PUT: 既存のリソースを完全に置き換えるために使用します。POSTと同様にリクエストボディを含むことが一般的です。
- DELETE: 特定のリソースを削除するために使用します。通常、リクエストボディは含みませんが、APIによっては含める場合もあります。
- PATCH: 既存のリソースの一部を更新するために使用します。リクエストボディには更新する部分のデータを含めます。
“`javascript
// 例11: PUTリクエスト (既存リソースの更新)
const updateData = {
title: ‘更新された記事タイトル’,
body: ‘この記事はPUTリクエストで更新されました。’,
userId: 1 // userId は通常変更しないが、jsonplaceholderの仕様に合わせて含める
};
fetch(‘https://jsonplaceholder.typicode.com/posts/1’, { // IDが1の投稿を更新
method: ‘PUT’,
headers: {
‘Content-Type’: ‘application/json; charset=UTF-8’
},
body: JSON.stringify(updateData)
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
return response.json();
})
.then(data => {
console.log(‘Post updated:’, data);
})
.catch(error => {
console.error(‘Failed to update post:’, error);
});
// 例12: DELETEリクエスト (リソースの削除)
fetch(‘https://jsonplaceholder.typicode.com/posts/1’, { // IDが1の投稿を削除
method: ‘DELETE’
// DELETEは通常ボディを含まない
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
// DELETE成功時はボディが空の場合が多いので、text()などで読むか、読まない
// 例: 成功時は空のオブジェクト {} を返すAPIの場合
// return response.json();
console.log(‘Post deleted successfully. Status:’, response.status); // 200 OK または 204 No Content など
})
// .then(data => { console.log(‘Response body after delete:’, data); }) // ボディがある場合のみ
.catch(error => {
console.error(‘Failed to delete post:’, error);
});
“`
これらのメソッドもGETやPOSTと同様に、fetch()
関数に適切な method
オプションと、必要に応じて headers
や body
オプションを指定するだけです。Promiseベースの処理、response.ok
によるHTTPエラーチェック、.catch()
によるエラー捕捉といった基本的なエラーハンドリングは共通して適用されます。
Part 7: Fetch APIとasync/await:現代的な非同期処理
Fetch APIはPromiseベースであるため、ES2017で導入された async
関数と await
キーワードと非常に相性が良いです。async/await
を使うことで、Promiseチェーンを記述するよりもさらに同期処理に近い、読みやすいコードで非同期処理を書くことができます。
async
関数は必ずPromiseを返します。await
キーワードは async
関数の中でしか使えません。await
は、Promiseが解決されるまで関数の実行を一時停止し、Promiseが解決された値を取得します。Promiseが拒否された場合は、エラーがスローされます。
Fetch APIを async/await
と組み合わせる基本的なパターンは以下のようになります。
``javascript
https://jsonplaceholder.typicode.com/posts/${postId}`);
// async関数内でFetch APIを使う例
async function fetchPost(postId) {
try {
// await は Promise が解決されるのを待つ
// fetch() が返す Promise が解決され、Response オブジェクトが得られるまで待機
const response = await fetch(
// レスポンスヘッダーが正常に取得できたら、HTTPエラーをチェック
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
// response.json() が返す Promise が解決され、パースされたデータが得られるまで待機
const data = await response.json();
// データが取得できたら、それを返す
return data;
} catch (error) {
// fetch() の失敗(ネットワークエラー)または await で待機中に発生したエラー(HTTPエラー検出後のthrowなど)を捕捉
console.error(‘Error fetching post:’, error);
// エラーをさらに伝播させる場合は、throw error;
throw error; // または特定のエラーオブジェクトを返す/throwする
}
}
// async 関数を呼び出して実行
fetchPost(1)
.then(post => {
console.log(‘Post data using async/await:’, post);
})
.catch(error => {
console.error(‘Error from async function:’, error);
});
// 別のIDで試す
fetchPost(9999) // 存在しないID
.then(post => {
console.log(‘Post data using async/await:’, post); // ここは実行されないはず
})
.catch(error => {
console.error(‘Error from async function (for ID 9999):’, error.message); // “HTTP error! Status: 404” が表示されるはず
});
“`
このコードでは、以下の流れで処理が進みます。
fetchPost(postId)
関数が呼び出されます。これはasync
関数なので、すぐに Promise を返します。- 関数内部で
await fetch(...)
が実行されます。fetch()
が Promise を返すので、Promiseが解決されるまでここで一時停止します。解決されると、response
変数にResponse
オブジェクトが入ります。 response.ok
でHTTPエラーをチェックし、エラーの場合はthrow new Error(...)
でエラーをスローします。エラーがスローされると、以降の処理はスキップされ、try...catch
ブロックのcatch
部分にジャンプします。- HTTPエラーがなければ、
await response.json()
が実行されます。response.json()
が Promise を返すので、ボディの読み取りとパースが完了するまで一時停止します。完了すると、data
変数にパースされたデータが入ります。 - データが取得できたら、関数はその
data
をreturn
します。async
関数からのreturn
は、その関数が返した Promise をその返り値で解決します。 try...catch
ブロックは、try
ブロック内で発生したエラー(ネットワークエラーや、手動でthrow
したHTTPエラーなど)を捕捉します。捕捉したエラーはcatch
ブロックで処理されます。catch
ブロック内で再度throw error;
することで、エラーを呼び出し元(この例ではfetchPost(...).catch(...)
)に伝播させることができます。
async/await
を使うことで、Promiseチェーンの .then()
を重ねるよりも直感的に、上から下へ処理が流れるようなコードになります。特に複数の非同期処理を順番に実行する必要がある場合(例: ユーザー情報を取得してからそのユーザーの投稿一覧を取得するなど)に、コードの可読性が大幅に向上します。
``javascript
https://jsonplaceholder.typicode.com/users/${userId}
// 例13: async/await を使った複数の非同期処理
async function getUserAndPosts(userId) {
try {
// 1. ユーザー情報を取得
const userResponse = await fetch();
HTTP error fetching user! Status: ${userResponse.status}`);
if (!userResponse.ok) {
throw new Error(
}
const user = await userResponse.json();
console.log(‘User fetched:’, user.name);
// 2. そのユーザーの投稿一覧を取得
const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
if (!postsResponse.ok) {
throw new Error(`HTTP error fetching posts! Status: ${postsResponse.status}`);
}
const posts = await postsResponse.json();
console.log(`Fetched ${posts.length} posts for ${user.name}`);
// ユーザー情報と投稿一覧をまとめて返す
return { user, posts };
} catch (error) {
console.error(‘Failed to fetch user or posts:’, error);
throw error; // エラーを呼び出し元に伝播
}
}
getUserAndPosts(1)
.then(({ user, posts }) => {
console.log(‘Successfully fetched user and posts:’);
console.log(‘User:’, user);
console.log(‘Posts:’, posts);
})
.catch(error => {
console.error(‘Operation failed:’, error);
});
getUserAndPosts(999) // 存在しないユーザーID
.then(…) // ここは実行されない
.catch(error => {
console.error(‘Operation failed for user 999:’, error.message); // エラーメッセージが表示される
});
“`
このように、async/await
はFetch APIとの組み合わせで非常に強力です。現代のJavaScript開発では、Fetch APIを使う際は積極的に async/await
を利用することが推奨されます。
Part 8: Fetch APIの応用的な使い方(基本の範囲で)
ここからは、Fetch APIの基本的なオプションをいくつか掘り下げて見ていきます。これらのオプションを使うことで、リクエストの挙動をより細かく制御できます。
リクエストヘッダーの詳細設定
headers
オプションは、Headers
オブジェクトまたは通常のJavaScriptオブジェクトで指定できます。特定のヘッダーを追加したり、デフォルトでブラウザが追加するヘッダーを上書きしたりできます。
よく使われるヘッダーの例:
Content-Type
: リクエストボディのメディアタイプ(データの形式)を示します(例:application/json
,application/x-www-form-urlencoded
,multipart/form-data
)。Accept
: クライアントが受け入れ可能なレスポンスのメディアタイプを指定します(例:application/json
,text/html
)。Authorization
: 認証情報(トークンなど)を送信します(例:Bearer your_token_here
)。
``javascript
Bearer ${yourAuthToken}` // 認証トークンなど
// 例14: カスタムヘッダーの追加
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Accept': 'application/json', // JSONレスポンスを希望
'X-Requested-With': 'Fetch' // カスタムヘッダー例
// 'Authorization':
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
// Headers オブジェクトを使う場合
const myHeaders = new Headers();
myHeaders.append(‘Content-Type’, ‘application/xml’); // XML形式で送信する場合など
myHeaders.append(‘X-Api-Key’, ‘your_api_key’);
fetch(‘https://api.example.com/resource’, {
method: ‘POST’,
headers: myHeaders, // Headers オブジェクトを渡す
body: ‘
})
.then(…)
.catch(…);
“`
Headers
オブジェクトを使うと、ヘッダーの追加、取得、削除などの操作をメソッド(append
, get
, delete
など)で行うことができます。単に静的なヘッダーを指定する場合は、JavaScriptオブジェクトで十分です。
認証情報(Cookieなど)の取り扱い (credentials
オプション)
ブラウザは通常、クロスオリジンリクエストを行う際にCookieやHTTP認証情報(Basic認証など)を自動では送信しません。これはセキュリティ上の理由からです。これらの認証情報をリクエストに含めたい場合は、credentials
オプションを指定する必要があります。
omit
(デフォルト): 認証情報を送信しない。same-origin
: 同じオリジンへのリクエストでのみ認証情報を送信する。クロスオリジンリクエストでは送信しない。include
: クロスオリジンを含むすべてのリクエストで認証情報を送信する。
クロスオリジンリクエストでCookieなどの認証情報を送信したい場合は、サーバー側で Access-Control-Allow-Credentials: true
ヘッダーをレスポンスに含める必要があります。サーバー側でこのヘッダーが許可されていない場合、credentials: 'include'
を指定したリクエストは失敗(ネットワークエラー)します。
javascript
// 例15: Cookieを含むクロスオリジンリクエスト
fetch('https://api.other-domain.com/data', {
method: 'GET',
credentials: 'include' // このオプションを指定すると、Cookieなどが含まれる
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Data with credentials:', data);
})
.catch(error => {
console.error('Error fetching with credentials:', error);
console.error('Possible CORS issue or server not allowing credentials:', error.message);
});
credentials: 'include'
の使用は、意図しない情報漏洩を防ぐため慎重に行う必要があります。信頼できるオリジンへのリクエストにのみ使用してください。
リクエストの中断 (AbortController
)
時間がかかるリクエストや、ユーザー操作によって不要になったリクエストを途中でキャンセルしたい場合があります。Fetch API自体にはタイムアウトを直接設定するオプションはありませんが、AbortController
インターフェースと組み合わせてリクエストを中断することができます。
AbortController
は AbortSignal
オブジェクトを持っており、これを fetch()
のオプションの signal
プロパティに渡します。AbortController
の abort()
メソッドを呼び出すと、関連付けられた AbortSignal
が発火し、それに紐づいたFetchリクエストが中断されます。リクエストが中断されると、fetch()
が返した Promise はエラー(AbortError
)と共に拒否されます。
“`javascript
// 例16: AbortControllerを使ったリクエストの中断
const controller = new AbortController();
const signal = controller.signal;
fetch(‘https://jsonplaceholder.typicode.com/posts’, {
method: ‘GET’,
signal: signal // signal オプションで AbortSignal を渡す
})
.then(response => {
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status}
);
}
return response.json();
})
.then(data => {
console.log(‘Data received before abort:’, data);
})
.catch(error => {
// リクエストが中断された場合は AbortError になる
if (error.name === ‘AbortError’) {
console.log(‘Fetch request aborted successfully’);
} else {
console.error(‘Fetch error (not aborted):’, error);
}
});
// 例: 5秒後にリクエストを中断する
setTimeout(() => {
console.log(‘Aborting fetch request…’);
controller.abort(); // リクエストを中断
}, 5000);
“`
この例では、Fetchリクエストを開始し、同時に5秒後に controller.abort()
が呼び出されるタイマーを設定しています。もしリクエストが5秒以内に完了すれば通常の .then()
が実行されます。5秒経過しても完了していなかった場合、abort()
が呼び出され、Fetchリクエストは中断され、.catch()
ブロックが実行され、AbortError
が捕捉されます。
これを応用すれば、例えばユーザーが検索中に新しい入力をした際に、前の検索リクエストをキャンセルして新しいリクエストを開始する、といった処理が実現できます。また、タイムアウト処理としても利用可能です。例えば、setTimeout
で一定時間後に abort()
を呼び出すようにすれば、擬似的なタイムアウトを実現できます。
“`javascript
// 例17: AbortControllerを使ったタイムアウト処理
function fetchWithTimeout(url, options, timeout = 10000) { // デフォルト10秒
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => {
controller.abort(); // タイムアウト時に中断
}, timeout);
return fetch(url, {
…options, // 元のオプションをスプレッド構文で展開
signal: signal // signal オプションを追加または上書き
})
.finally(() => {
// Promise が解決または拒否されたらタイマーをクリア
clearTimeout(timeoutId);
});
}
// タイムアウトを試す(例えば、応答が遅いURLや、単に短いタイムアウトを設定)
fetchWithTimeout(‘https://jsonplaceholder.typicode.com/posts/1’, {}, 100) // 100ミリ秒のタイムアウト
.then(response => response.json())
.then(data => {
console.log(‘Fetched data within timeout:’, data); // タイムアウトしなかった場合
})
.catch(error => {
if (error.name === ‘AbortError’) {
console.error(‘Fetch request timed out’); // タイムアウトした場合
} else {
console.error(‘Fetch error:’, error);
}
});
“`
fetchWithTimeout
関数は、指定されたタイムアウト時間内にFetchリクエストが完了しなかった場合に AbortError
を発生させます。finally()
ブロックは、Promiseが解決されるか拒否されるかに関わらず実行されるため、ここで安全にタイムアウトタイマーをクリアできます。
モード (mode
オプション – cors, no-cors, same-origin)
mode
オプションは、クロスオリジンリクエストの扱い方を制御します。
cors
(デフォルト): クロスオリジンリクエストを許可し、CORSプロトコルに従います。サーバーが適切なCORSヘッダーを返さない場合、リクエストは完了してもブラウザがレスポンスを使用できないようにブロックします。これがほとんどのAPIリクエストで必要となるモードです。no-cors
: クロスオリジンリソースを匿名で(認証情報なしで)取得することを許可しますが、スクリプトからレスポンスの一部(特にボディ)にアクセスできなくなります。主にService Workerから他のオリジンのリソースを取得し、それをキャッシュするなど、特殊な用途で使用されます。レスポンスのステータスコードは常に200と報告され、ヘッダーも一部しかアクセスできません。same-origin
: 同じオリジンへのリクエストのみを許可します。クロスオリジンリクエストはネットワークエラーになります。
通常、明示的に指定する必要はありませんが、Service Worker開発など特殊なケースでは重要になります。
javascript
// 例18: mode オプション (通常はデフォルトのcorsで十分)
fetch('https://api.example.com/data', {
mode: 'cors' // 明示的に CORS モードを指定 (デフォルトと同じ)
// mode: 'no-cors' // no-cors モード (レスポンスボディなどにアクセスできなくなる)
// mode: 'same-origin' // 同一オリジンのみ許可
})
.then(response => {
// mode: 'cors' または 'same-origin' の場合、通常通りレスポンスを処理
if (response.ok) { ... }
// mode: 'no-cors' の場合、response.ok は常に true (Status: 0)、
// headers は一部のみ、body にアクセスできない制限がある
})
.catch(error => {
// mode: 'same-origin' でクロスオリジンにアクセスした場合などにエラー
});
多くのウェブアプリケーションでは、デフォルトの mode: 'cors'
が適切です。
Part 9: XMLHttpRequest vs Fetch API:改めて比較
Fetch APIの基本的な使い方を見てきましたが、ここで改めてXMLHttpRequest (XHR) とFetch APIを比較し、Fetch APIの優位性を再確認しましょう。
GETリクエストの比較
“`javascript
// XMLHttpRequestでのGETリクエスト
function fetchWithXHR_GET(url, callback) {
const xhr = new XMLHttpRequest();
xhr.open(‘GET’, url, true); // trueは非同期を意味する
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
// 成功 (2xx ステータス)
try {
const data = JSON.parse(xhr.responseText);
callback(null, data); // エラーなし、データを渡す
} catch (e) {
callback(e, null); // JSONパースエラー
}
} else {
// HTTPエラー (4xx, 5xx など)
callback(new Error(HTTP error! Status: ${xhr.status}
), null);
}
};
xhr.onerror = function() {
// ネットワークエラー
callback(new Error(‘Network error occurred’), null);
};
xhr.send(); // リクエストを送信
}
fetchWithXHR_GET(‘https://jsonplaceholder.typicode.com/posts/1’, (error, data) => {
if (error) {
console.error(‘XHR Error:’, error);
} else {
console.log(‘XHR Data:’, data);
}
});
// Fetch APIでのGETリクエスト (async/await スタイル)
async function fetchWithFetch_GET(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(HTTP error! Status: ${response.status}
);
}
const data = await response.json();
return data;
} catch (error) {
console.error(‘Fetch API Error:’, error);
throw error; // エラーを再スロー
}
}
fetchWithFetch_GET(‘https://jsonplaceholder.typicode.com/posts/1’)
.then(data => {
console.log(‘Fetch API Data:’, data);
})
.catch(error => {
// fetchWithFetch_GET 内でエラーはすでにログ出力されているが、
// 呼び出し元で捕捉することも可能
console.error(‘Fetch API call failed’);
});
“`
XHRの例では、成功時 (onload
) とエラー時 (onerror
) のイベントハンドラーを設定し、成功時にはさらにHTTPステータスコードをチェックして、レスポンステキストをパースするという手順が必要です。コールバックベースのため、処理の連鎖や複数のエラー種別(ネットワーク vs HTTP vs パース)のハンドリングが少し煩雑になりがちです。
一方、Fetch APIと async/await
を組み合わせた例では、コードが上から下に流れるように書けます。await
で非同期処理の完了を待ち、エラーは try...catch
でまとめて捕捉できます。HTTPエラーのチェックは依然として必要ですが、コード全体が Promise/async-await のパターンに沿っているため、構造がシンプルで理解しやすくなっています。
POSTリクエストの比較
“`javascript
// XMLHttpRequestでのPOSTリクエスト
function fetchWithXHR_POST(url, data, callback) {
const xhr = new XMLHttpRequest();
xhr.open(‘POST’, url, true);
xhr.setRequestHeader(‘Content-Type’, ‘application/json;charset=UTF-8’); // ヘッダー手動設定
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const responseData = JSON.parse(xhr.responseText);
callback(null, responseData);
} catch (e) {
callback(e, null); // JSONパースエラー
}
} else {
callback(new Error(HTTP error! Status: ${xhr.status}
), null);
}
};
xhr.onerror = function() {
callback(new Error(‘Network error occurred’), null);
};
xhr.send(JSON.stringify(data)); // データを文字列化して送信
}
const postDataXHR = { title: ‘XHR Post’, body: ‘Testing XHR’, userId: 1 };
fetchWithXHR_POST(‘https://jsonplaceholder.typicode.com/posts’, postDataXHR, (error, responseData) => {
if (error) {
console.error(‘XHR Post Error:’, error);
} else {
console.log(‘XHR Post Response:’, responseData);
}
});
// Fetch APIでのPOSTリクエスト (async/await スタイル)
async function fetchWithFetch_POST(url, data) {
try {
const response = await fetch(url, {
method: ‘POST’,
headers: {
‘Content-Type’: ‘application/json;charset=UTF-8’
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const responseData = await response.json();
return responseData;
} catch (error) {
console.error(‘Fetch API Post Error:’, error);
throw error;
}
}
const postDataFetch = { title: ‘Fetch API Post’, body: ‘Testing Fetch API’, userId: 1 };
fetchWithFetch_POST(‘https://jsonplaceholder.typicode.com/posts’, postDataFetch)
.then(responseData => {
console.log(‘Fetch API Post Response:’, responseData);
})
.catch(error => {
console.error(‘Fetch API Post call failed’);
});
“`
POSTリクエストにおいても、Fetch APIはオプションオブジェクトに設定を集約できるため、XHRに比べてより簡潔に記述できます。特にヘッダーやボディの設定が直感的です。
Fetch APIの優位性まとめ
- Promiseベース: 非同期処理が管理しやすく、
.then()
,.catch()
,async/await
との相性が抜群。 - シンプル:
fetch()
関数一つでリクエストを開始でき、設定はオプションオブジェクトにまとまる。 - 柔軟性: リクエスト/レスポンスの各部分(Header, Bodyなど)がオブジェクトとして抽象化されており扱いやすい。
- Web標準との連携: Service WorkerなどのモダンAPIと共通のインターフェースを持つ。
- ストリーム対応: 大容量のデータを扱う際に有用(基本的な使い方からは外れますが)。
歴史的な理由や特定のブラウザ互換性の問題からXHRが使われる場面もゼロではありませんが、現代のほとんどのWeb開発においては、非同期通信の新しいコードを書くなら迷わずFetch APIを選択すべきです。
Part 10: 実践的なヒントと注意点
Fetch APIを使う上で、いくつか実践的なヒントと注意点があります。
- クロスブラウザ対応: Fetch APIは主要なモダンブラウザ(Chrome, Firefox, Safari, Edgeなど)で広くサポートされています。ただし、古いIEなど一部のブラウザではサポートされていません。もしこれらのブラウザをサポートする必要がある場合は、Fetch APIのポリフィル(Polyfill)を利用するか、XMLHttpRequestまたはjQueryのAjaxメソッドなどを代替として検討する必要があります。Caniuseなどのサイトで最新のサポート状況を確認しましょう。
- IE11のサポート: IE11はFetch APIをネイティブにサポートしていません。IE11をサポートする必要がある場合は、
whatwg-fetch
などのポリフィルを使用するのが一般的な方法です。 - 代替ライブラリ: Fetch APIは低レベルなAPIであり、以下のような機能は標準では提供されていません。
- プログレス表示: アップロード/ダウンロードの進捗状況をリアルタイムに取得する機能は、Fetch API単体では組み込みで提供されていません。XHRには
onprogress
イベントがありましたが、Fetch APIではReadableStream
を使うなど、より複雑な方法が必要になります。 - リクエストの自動再試行: ネットワークエラーやサーバーエラー時に自動的にリクエストを再試行する機能はありません。
- タイムアウト: 前述の通り、直接的なタイムアウトオプションはありませんが、
AbortController
で擬似的に実現できます。 - 応答の変換・加工: インターセプターのような、リクエスト/レスポンスをグローバルに横断して処理する仕組みは標準ではありません。
これらの機能が必要な場合や、より高度なラッパーが必要な場合は、Axiosのようなサードパーティ製のHTTPクライアントライブラリの利用も検討できます。AxiosはXHRベースまたはFetch APIベースで動作し、これらの高レベルな機能を提供します。ただし、Fetch APIの基本的な使い方を理解することは、これらのライブラリを使う上でも基盤となります。
- プログレス表示: アップロード/ダウンロードの進捗状況をリアルタイムに取得する機能は、Fetch API単体では組み込みで提供されていません。XHRには
- CORSの理解: Fetch APIを使う上で最もつまづきやすいのがCORS (Cross-Origin Resource Sharing) です。異なるオリジン(プロトコル、ドメイン、ポートのいずれかが異なる)に対してFetchリクエストを行う場合、ブラウザはセキュリティ上の制限をかけます。サーバー側が適切なCORSヘッダー(
Access-Control-Allow-Origin
など)を返さない限り、リクエスト自体は送信されても、ブラウザがレスポンスをJavaScriptに引き渡さないという形でエラーとなります。特に開発中は、バックエンドAPIがフロントエンドの開発サーバーからのリクエストを許可するように設定されているか確認が必要です。 - Cookieとセッション管理:
credentials: 'include'
オプションを使用することで、クロスオリジンリクエストでもCookieなどの認証情報を送信できます。ただし、サーバー側でAccess-Control-Allow-Credentials: true
とAccess-Control-Allow-Origin
に特定のオリジン(*
は使用できない)を設定する必要があります。セキュアなアプリケーションでは、Cookieベースのセッション管理だけでなく、Authorization
ヘッダーを使ったトークンベースの認証も一般的です。 - リクエストボディの再利用不可:
Response
オブジェクトのボディ読み取りメソッド(json()
,text()
など)は一度しか呼び出せません。複数回呼び出そうとするとエラーになります。もし同じレスポンスボディの複数の形式で扱いたい場合は、一度読み取ったデータを変数に格納して再利用する必要があります。
これらの点に注意しながらFetch APIを使用することで、より堅牢で効率的な非同期通信を実装できます。
まとめ
この記事では、JavaScriptのFetch APIの基本的な使い方から、GET/POSTリクエスト、エラーハンドリング、async/await
との連携、そしていくつかの応用的なオプションまでを詳細に解説しました。
Fetch APIは、Promiseベースの非同期処理、シンプルで統一されたインターフェース、そしてWeb標準との整合性という点において、従来のXMLHttpRequestに比べて大きな優位性を持っています。現代のWeb開発において、外部リソースとの非同期通信を行う際の標準的な技術として、Fetch APIは必須のスキルと言えるでしょう。
この記事を通じて、あなたは以下の点を習得したはずです。
- Fetch APIがなぜ優れているのか(XHRとの比較)。
fetch()
関数の基本的な使い方とPromiseの役割。Response
オブジェクトの主要なプロパティとボディ読み取りメソッド。- GETリクエストでデータを取得し、クエリパラメータを追加する方法。
- POSTリクエストでデータを送信し、JSONやFormDataを扱う方法。
- ネットワークエラーとHTTPエラーの違い、そして効果的なエラーハンドリングの手法(
response.ok
,.catch()
,throw Error
)。 - PUTやDELETEなど、他のHTTPメソッドの使い方。
async/await
を使ってFetch APIをより読みやすく、書きやすくする方法。headers
,credentials
,signal
(AbortController) といった応用的なオプションの基本的な使い方。- Fetch APIを使う上での注意点(CORS, Polyfill, 代替ライブラリなど)。
これらの知識を活かせば、あなたは自信を持ってJavaScriptで非同期通信を実装できるようになります。ぜひ、実際にコードを書いて様々なAPIとの連携を試してみてください。
Fetch APIは、シングルページアプリケーション(SPA)やAPI連携を多用する現代的なWebアプリケーション開発において、中心的な役割を果たします。この記事が、あなたのFetch APIマスターへの第一歩となることを願っています。
Happy Coding!