導入
フロントエンド開発において、Fetch APIはデータの取得に欠かせない存在です。しかし、「レスポンスボディを一度読み取ると、二度と読み取れない」という仕様に直面し、エラーやバグに悩んだ経験はありませんか?例えば、取得したデータをログに出力してからJSONとしてパースしようとした際、`TypeError: Body already read`が発生するケースです。この課題を解決し、ストリームを再利用可能にするのが Response.prototype.clone です。
基礎知識
Fetch APIで返される `Response` オブジェクトのボディは「ReadableStream」として実装されています。ストリームの性質上、一度データが消費されると、その中身は空になります。これはメモリ効率を最適化するための仕組みですが、開発時には「デバッグ用にログを出し、その後本来の処理でパースする」といった柔軟なデータ利用を阻害します。`clone()` メソッドは、元のレスポンスを消費することなく、そのコピーを生成することで、複数の箇所でデータを安全に扱えるようにします。
実装/解決策
`response.clone()` を呼び出すと、元のレスポンスと全く同じ内容を持つ新しい `Response` オブジェクトが作成されます。重要なのは、必ず元の処理の前にクローンを作成することです。クローン後のオブジェクトは独立しているため、片方を読み取ってももう片方には影響しません。
サンプルプログラム
以下のコードは、APIから取得したレスポンスをデバッグ表示しつつ、同時にJSONとして取得する方法です。
async function fetchData(url) {
try {
const response = await fetch(url);
// 1. レスポンスを消費する前にクローンを作成
// これにより、元のresponseはそのまま保持される
const responseClone = response.clone();
// 2. クローン側をデバッグ用にテキストとして読み取る
const text = await responseClone.text();
console.log("デバッグ用ログ:", text);
// 3. 元のresponseはまだ消費されていないため、JSONとしてパース可能
const data = await response.json();
return data;
} catch (error) {
console.error("通信エラーが発生しました:", error);
}
}
応用・注意点
実務における注意点は、メモリ消費量です。`clone()` は単なる参照のコピーではなく、ボディのコピーをメモリ上に保持するため、非常に巨大なデータ(数GBのバイナリデータなど)を扱う場合、安易にクローンを行うとブラウザのメモリを圧迫し、クラッシュの原因になります。
また、一度ボディを読み取った後のオブジェクトに対して `clone()` を実行しても、空のボディがコピーされるだけなので意味がありません。必ず「ボディを読み取る前」に実行することを徹底してください。Service Workerなどでレスポンスをキャッシュしつつ、メインスレッドへ値を返す際にも非常に有用なテクニックですので、ぜひ活用してください。