fetchとは、URLを指定してHTTPリクエストを送り、結果を Promise で受け取る JavaScript 標準の関数です。 ブラウザだけでなく Node.js 18 以降でもインストールなしで使え、await fetch(url) でレスポンスを受け取り、await res.json() で中身を取り出す、という2段階が基本です。

この記事では、GET・POSTの基本から、タイムアウト(AbortSignal.timeout / AbortSignal.any)、リトライ、並列実行、ストリーミング、ダウンロード進捗、Cookie、CORSまでを 用途別のコピペ関数 にまとめました。すべて手元に立てたテストサーバーに対して Node.js v22.22.2 と Chromium 141(Playwright)の両方で実行し、出力をそのまま掲載 しています。response.ok を確認しないと 404 でも処理が進む、json() を2回呼ぶとエラーになる、といったつまずきも、実際のエラー文で確認できます。


fetchでやりたいことの早見表

「やりたいこと」から、fetchの書き方と注意点を引けるようにしました。 各行の関数は本文にコピペ用で載せています。

やりたいこと 書き方の要点 注意点
JSONを取得(GET) const res = await fetch(url); const data = await res.json(); res.ok を確認する(404/500 では例外にならない)
クエリ文字列を付ける url.search = new URLSearchParams(params) 自分で ?q= を連結せずエンコードを任せる
JSONを送る(POST) method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) Content-Type がないとサーバーがJSONとして読まないことがある
更新・削除(PUT/DELETE) method: 'PUT' / 'DELETE' 204 は本文なし。json() を呼ばない
フォーム・ファイル送信 body: new FormData() Content-Type を書かない(境界文字列を自動で付けるため)
認証ヘッダー headers: { Authorization: 'Bearer ...' } 401 は res.ok === false で判定
タイムアウト signal: AbortSignal.timeout(ms) エラー名は TimeoutError
タイムアウト+キャンセル signal: AbortSignal.any([ac.signal, AbortSignal.timeout(ms)]) ユーザー中断は AbortError
失敗時に再試行 5xx・429・通信エラーだけ指数バックオフで再送 4xx を再送しても結果は同じ
複数を同時に取得 Promise.all / 失敗も集めるなら Promise.allSettled all は1つ失敗で全体が失敗
少しずつ読む res.body.pipeThrough(new TextDecoderStream()).getReader() 行の途中で分割されるのでバッファする
ダウンロード進捗 Content-Length と reader.read() の合計で計算 圧縮転送だと合計と一致しないことがある
Cookieを送る 同じオリジンは既定で送る。別オリジンは credentials: 'include' 別オリジンはサーバー側の許可も必要
別オリジンのAPIを呼ぶ サーバーが Access-Control-Allow-Origin を返す CORSはブラウザだけの制限(Nodeは対象外)

fetchのオプション早見

オプション よく使う値 意味
method 'GET'(既定)/ 'POST' / 'PUT' / 'PATCH' / 'DELETE' HTTPメソッド
headers { 'Content-Type': 'application/json' } 送るヘッダー
body 文字列 / FormData / URLSearchParams / Blob 送る本文(GETでは指定不可)
signal AbortSignal.timeout(5000) 中断用の合図
credentials 'same-origin'(既定)/ 'include' / 'omit' Cookieを送るか
mode 'cors'(既定)/ 'same-origin' / 'no-cors' 別オリジンへの送り方
cache 'no-store' / 'reload' など ブラウザのHTTPキャッシュの使い方

Responseでよく使うもの

プロパティ・メソッド 返すもの
res.ok ステータスが 200〜299 なら true
res.status / res.statusText 404 / 'Not Found'
res.headers.get('Content-Type') ヘッダーの値
res.json() / res.text() / res.blob() / res.arrayBuffer() / res.formData() 本文(どれか1回だけ)
res.body ReadableStream(少しずつ読む)
res.clone() 本文を2回読みたいときの複製

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JavaScript fetchの使い方|用途別コピペ関数と実行結果」


検証環境

項目 バージョン・内容
Node.js v22.22.2(fetch は標準搭載)
ブラウザ Chromium 141.0.7390.37(Playwright 1.56.0)
テストサーバー Node の http で作成。localhost:7150(API)と localhost:7151(別オリジン確認用)
比較用 axios 1.20.0

以下の関数は1つのファイル(lib.mjs)にまとめ、Node では import、ブラウザでは import('/lib.mjs') で読み込んで同じコードを実行しました。Node とブラウザで結果が違ったものは、両方の出力を載せています。


fetchの基本:GETでJSONを取得する

fetchは「レスポンスが届いた時点」と「本文を読み終えた時点」の2回待ちます。 1回目の await fetch() ではステータスとヘッダーだけが分かり、本文は await res.json() で読み出します。

// 共通:ステータスを確認してJSONを返す
export async function getJSON(url, options = {}) {
  const res = await fetch(url, options);
  if (!res.ok) {
    throw new Error(`HTTP ${res.status} ${res.statusText}`);
  }
  return res.json();
}

const data = await getJSON('http://localhost:7150/api/items');
console.log(data);

実行結果(Node v22.22.2/Chromium 141 とも同じ):

{"query":{},"items":[{"id":1,"name":"ノート"},{"id":2,"name":"ペン"}]}

.then() でつなぐ書き方もありますが、エラー処理を try/catch でまとめて書ける async/await が読みやすいので、この記事ではすべて await で書いています。


よくある落とし穴(実際のエラー文つき)

404 や 500 でも例外にならない(response.ok を見る)

fetchが例外を投げるのは「通信そのものが失敗したとき」だけで、404 や 500 は「正常に届いたレスポンス」として扱われます。 存在しないURLを res.ok を見ずに処理すると、エラー用のJSONをそのまま正常データとして受け取ります。

// 直す前:ok を見ない
const res = await fetch('http://localhost:7150/api/missing');
const data = await res.json();
console.log({ ok: res.ok, status: res.status, data });
{"ok":false,"status":404,"data":{"error":"not found"}}
// 直した後:getJSON(上で定義)を使う
try {
  await getJSON('http://localhost:7150/api/missing');
} catch (e) {
  console.log(`${e.name}: ${e.message}`);
}
Error: HTTP 404 Not Found

fetchで起きる失敗の種類と見分け方

状況 fetchの結果 実際のエラー(Node 22) 実際のエラー(Chromium 141)
404 / 500 成功(res.ok === false) 例外なし 例外なし
サーバーに接続できない 例外 TypeError: fetch failed(cause.code は ECONNREFUSED) TypeError: Failed to fetch
CORSで拒否 例外 (Nodeでは発生しない) TypeError: Failed to fetch
タイムアウト 例外 TimeoutError TimeoutError
JSONでない本文を json() json() が例外 SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON 同じ

Node の fetch failed は詳細が err.cause に入るので、ログには err.cause?.code も出すと原因が分かりやすくなります。

json() を2回呼ぶとエラーになる

レスポンスの本文はストリームなので、一度しか読めません。

const res = await fetch('http://localhost:7150/api/items');
await res.json();
await res.json(); // 2回目
環境 実際のエラー
Node v22.22.2 TypeError: Body is unusable: Body has already been read
Chromium 141 TypeError: Failed to execute 'json' on 'Response': body stream already read

ログ用とデータ用に2回読みたいときは、先に res.clone() で複製します。

const res = await fetch('http://localhost:7150/api/items');
const copy = res.clone();
const data = await res.json();      // データとして使う
const raw = await copy.text();      // ログに残す

HTMLが返ってきて json() が失敗する

メンテナンス画面などのHTMLに json() を使うと、SyntaxError: Unexpected token '<' になります。 先に res.headers.get('Content-Type') が application/json を含むか確かめ、違えば await res.text() の先頭をログに出すと原因が分かりやすくなります。


用途別コピペ関数集

クエリ文字列を付けてGETする(URLSearchParams)

URL と URLSearchParams に組み立てを任せると、日本語や & も正しくエンコードされます。

export async function searchItems(base, params) {
  const url = new URL('/api/items', base);
  url.search = new URLSearchParams(params).toString();
  return getJSON(url);
}

await searchItems('http://localhost:7150', { q: 'ノート ペン', page: 2, tag: 'a&b' });

組み立てられたURL(Node v22.22.2):

http://localhost:7150/api/items?q=%E3%83%8E%E3%83%BC%E3%83%88+%E3%83%9A%E3%83%B3&page=2&tag=a%26b

サーバーが受け取った値:

{"q":"ノート ペン","page":"2","tag":"a&b"}

tag=a&b を文字列連結で書くと tag=a と b に分かれてしまいますが、URLSearchParams なら a%26b とエンコードされ、そのまま届きました。数値の 2 は文字列 "2" として届く点も覚えておくと便利です。

JSONをPOSTする

export async function postJSON(url, data) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

await postJSON('http://localhost:7150/api/items', { name: '付箋', qty: 3 });

実行結果(Node/Chromium とも同じ):

{"received":{"name":"付箋","qty":3},"contentType":"application/json"}

body にはオブジェクトではなく JSON.stringify() した文字列を渡します。

PUTで更新、DELETEで削除する

export async function putJSON(url, data) {
  const res = await fetch(url, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

export async function deleteItem(url) {
  const res = await fetch(url, { method: 'DELETE' });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.status; // 204 No Content は本文なし → json() を呼ばない
}
PUT    → {"id":"1","updated":{"qty":5}}
DELETE → 204

204 No Content に json() を呼ぶと本文が空なので SyntaxError になります(Node v22.22.2 では SyntaxError: Unexpected end of JSON input、Chromium 141 では SyntaxError: Failed to execute 'json' on 'Response': Unexpected end of JSON input)。そのため削除APIではステータスだけを返しています。

フォームやファイルを送る(FormData)

FormData を body に渡すと、multipart/form-data と区切り文字(boundary)が自動で付きます。自分で Content-Type を書くと区切り文字が抜けるので、書かないのがポイントです。

試しに headers: { 'Content-Type': 'multipart/form-data' } を付けて同じ FormData を送ると、テストサーバー(Node の Response.formData() で解析)は TypeError: Failed to parse body as FormData. となり、400 を返しました。

export async function upload(url, file, title) {
  const fd = new FormData();
  fd.append('title', title);
  fd.append('file', file, file.name ?? 'memo.txt');
  const res = await fetch(url, { method: 'POST', body: fd });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

// ブラウザなら <input type="file"> の files[0] をそのまま渡せます
const file = new File(['在庫メモ'], 'memo.txt', { type: 'text/plain' });
await upload('http://localhost:7150/api/upload', file, '棚卸し');

実行結果(Node/Chromium とも同じ):

{"contentType":"multipart/form-data","fields":{"title":"棚卸し","file":{"name":"memo.txt","type":"text/plain","size":12}}}

size が 12 なのは、「在庫メモ」の4文字がUTF-8で各3バイトだからです。ファイルなしのフォーム(application/x-www-form-urlencoded)を送るなら、body: new URLSearchParams({ name: '付箋' }) と書けます。

認証ヘッダーを付ける

const data = await getJSON('http://localhost:7150/api/me', {
  headers: { Authorization: 'Bearer demo-token' },
});
付けた場合   → {"user":"demo"}
付けない場合 → Error: HTTP 401 Unauthorized

トークンはソースコードに直接書かず、ログイン後に受け取った値や環境変数から渡します。

タイムアウトを付ける(AbortSignal.timeout)

fetchにはタイムアウトのオプションがないため、signal に AbortSignal.timeout(ミリ秒) を渡します。 以前は AbortController と setTimeout を組み合わせていましたが、今は1行で書けます。

export async function getWithTimeout(url, ms) {
  try {
    return await getJSON(url, { signal: AbortSignal.timeout(ms) });
  } catch (err) {
    if (err.name === 'TimeoutError') return `タイムアウト(${ms}ms): ${err.message}`;
    if (err.name === 'AbortError') return `中断: ${err.message}`;
    throw err;
  }
}

// 3秒かかるAPIに1秒のタイムアウト
await getWithTimeout('http://localhost:7150/api/slow?ms=3000', 1000);
環境 実行結果
Node v22.22.2 タイムアウト(1000ms): The operation was aborted due to timeout(経過 1002ms)
Chromium 141 タイムアウト(1000ms): signal timed out

メッセージは環境で違うので、判定は err.name === 'TimeoutError' で行うと両方で動きます。

タイムアウトと「キャンセルボタン」を両立する(AbortSignal.any)

AbortSignal.any([...]) は、渡した合図のどれかが中断されたら中断される新しい合図を作ります。 検索のキャンセルボタンとタイムアウトを同時に効かせたいときに使います(Chrome 116、Firefox 124、Safari 17.4、Node 20.3 以降)。

export async function getCancelable(url, userSignal, ms) {
  const signal = AbortSignal.any([userSignal, AbortSignal.timeout(ms)]);
  try {
    return await getJSON(url, { signal });
  } catch (err) {
    return `${err.name}: ${err.message}`;
  }
}

const ac = new AbortController();
cancelButton.onclick = () => ac.abort(); // 検証では 300ms 後に abort() を呼んだ
await getCancelable('http://localhost:7150/api/slow?ms=3000', ac.signal, 1000);
状況 Node v22.22.2 Chromium 141
300ms でユーザーが中断 AbortError: This operation was aborted AbortError: signal is aborted without reason
500ms でタイムアウト TimeoutError: The operation was aborted due to timeout TimeoutError: signal timed out

err.name で「ユーザーが止めた(AbortError)」と「時間切れ(TimeoutError)」を区別でき、画面の表示を分けられます。

失敗したら再試行する(指数バックオフ)

再試行するのは、時間をおけば成功する見込みがあるもの(通信エラー、5xx、429 Too Many Requests)だけにします。 待ち時間を 200→400→800ms と倍にしていく方法を指数バックオフといい、少しゆらぎ(ランダムな加算)を足すと、多数のクライアントが同時に再送するのを避けられます。

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function fetchWithRetry(url, { retries = 3, baseDelay = 200, log = () => {} } = {}) {
  for (let attempt = 0; ; attempt++) {
    let res;
    try {
      res = await fetch(url);
    } catch (err) {
      // ネットワークエラー(接続できない等)は再試行の対象
      if (attempt >= retries) throw err;
      log(`${err.name} → 再試行`);
    }
    if (res) {
      if (res.ok) return res.json();
      // 5xx と 429 だけ再試行。それ以外の 4xx は送り直しても同じ結果
      const retryable = res.status >= 500 || res.status === 429;
      if (!retryable || attempt >= retries) throw new Error(`HTTP ${res.status}`);
      log(`HTTP ${res.status} → ${baseDelay * 2 ** attempt}ms 待って再試行`);
    }
    await sleep(baseDelay * 2 ** attempt + Math.random() * 100); // 200, 400, 800ms… +ゆらぎ
  }
}

2回目まで 503 を返し、3回目で成功するAPIに対して実行した結果(Node/Chromium とも同じ):

HTTP 503 → 200ms 待って再試行
HTTP 503 → 400ms 待って再試行
{"ok":true,"attempt":3}

404 のAPIに対しては再試行せず、すぐに HTTP 404 で失敗しました。POST を再試行する場合は、サーバー側で同じ注文が二重に登録されない仕組み(冪等性キーなど)があるか確認してからにします。

複数のAPIを同時に呼ぶ(Promise.all / allSettled)

順番に await すると待ち時間が足し算になりますが、Promise.all で同時に投げると最も遅いものの時間で済みます。

const results = await Promise.all(
  [1000, 1000, 1000].map((ms) => getJSON(`http://localhost:7150/api/slow?ms=${ms}`)),
);
1秒かかるAPI×3 → 合計 1003ms(順番に呼ぶと約3秒)

1つでも失敗すると Promise.all 全体が失敗します。 成功したものだけでも使いたいときは Promise.allSettled を使います。

const settled = await Promise.allSettled([
  getJSON('http://localhost:7150/api/items'),
  getJSON('http://localhost:7150/api/missing'),
]);
console.log(settled.map((r) => r.status === 'fulfilled' ? r.status : `${r.status}: ${r.reason.message}`));
[ 'fulfilled', 'rejected: HTTP 404 Not Found' ]

届いた分から少しずつ読む(ストリーミング)

res.body は ReadableStream なので、全部届くのを待たずに読み進められます。 1行1JSONの形式(NDJSON)を、200ms ごとに1行送るサーバーで試しました。

export async function readNDJSON(url, onLine) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
  let buf = '';
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buf += value;
    const lines = buf.split('\n');
    buf = lines.pop();              // 行の途中で切れた残りは次回に回す
    for (const line of lines) if (line) onLine(JSON.parse(line));
  }
}
Node v22.22.2 : step1 +201ms, step2 +400ms, step3 +601ms
Chromium 141  : step1 +202ms, step2 +402ms, step3 +602ms

サーバーが送った間隔のとおりに1行ずつ受け取れています。 res.json() を使うと、最後の行が届く約600ms後まで何も表示できません。

ダウンロードの進捗を表示する

export async function downloadWithProgress(url, onProgress) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const total = Number(res.headers.get('Content-Length')) || 0;
  const reader = res.body.getReader();
  const chunks = [];
  let received = 0;
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    chunks.push(value);
    received += value.length;
    onProgress(received, total);
  }
  return new Blob(chunks);
}

await downloadWithProgress('http://localhost:7150/api/download', (r, t) => {
  console.log(`${Math.round((r / t) * 100)}%`);
});

1MBのファイル(256KBずつ送信)での結果:

環境 進捗の呼び出し回数 最後の値 Blobのサイズ
Node v22.22.2 20回(6%, 12%, 19%, 25% … 100%) 100% 1,048,576
Chromium 141 8回 100% 1,048,576

1回に届く量(チャンクの大きさ)は環境によって変わるので、進捗の刻みは保証されません。また、gzip などで圧縮して送られると Content-Length は圧縮後の大きさになり、合計と一致しないことがあります。なお、fetch にはアップロードの進捗を取る標準的な方法がないため、送信の進捗バーが必要な場合は XMLHttpRequest の upload.onprogress を使います。

Cookieを送る(credentials)

ブラウザでは、同じオリジン(プロトコル・ホスト・ポートが同じ)へのリクエストには既定で Cookie が送られます。

await fetch('/api/login');                                  // Set-Cookie: sid=abc123; HttpOnly
await (await fetch('/api/whoami')).json();                  // 既定(same-origin)
await (await fetch('/api/whoami', { credentials: 'omit' })).json(); // 送らない
環境・設定 サーバーが受け取った Cookie
Chromium:既定(same-origin) sid=abc123
Chromium:credentials: 'omit' null
Chromium:res.headers.get('set-cookie') null(JavaScriptからは読めない)
Node:res.headers.getSetCookie() ['sid=abc123; Path=/; HttpOnly; SameSite=Lax']
Node:次のリクエスト null(Node の fetch は Cookie を自動保存しない)

別オリジンに Cookie を送るには credentials: 'include' が必要で、サーバー側も Access-Control-Allow-Credentials: true と、* ではない具体的な Access-Control-Allow-Origin を返す必要があります。 Node で Cookie を続けて送りたいときは、受け取った値を自分で Cookie ヘッダーに入れます。


CORSエラーを実際に出して理解する

CORS(オリジン間リソース共有)は、ブラウザが「別のオリジンのAPIの結果を、そのページのJavaScriptに渡してよいか」をサーバーの応答ヘッダーで確認する仕組みです。 localhost:7151 のページから localhost:7150 のAPIを呼んで確かめました(ポートが違うだけで別オリジンです)。

// http://localhost:7151 のページで実行
await fetch('http://localhost:7150/api/nocors'); // 許可ヘッダーなし
await fetch('http://localhost:7150/api/cors');   // Access-Control-Allow-Origin: http://localhost:7151
呼び出し Chromium 141 の結果
/api/nocors(許可ヘッダーなし) TypeError: Failed to fetch
/api/cors(許可あり) {"ok":true,"origin":"http://localhost:7151"}
/api/cors に JSON を POST(事前確認 OPTIONS に許可を返す) ステータス 200
/api/nocors に mode: 'no-cors' type: "opaque", status: 0, ok: false(中身は読めない)
Node から /api/nocors {"secret":"same-origin only"}(Node にはCORSの制限がない)

ブラウザの開発者ツールのコンソールには、次のメッセージが出ました。

Access to fetch at 'http://localhost:7150/api/nocors' from origin 'http://localhost:7151' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

JavaScript側の例外は Failed to fetch だけで、理由はコンソールにしか出ません。 CORSの対処はサーバー側で行います。

方法 内容
APIサーバーで許可する Access-Control-Allow-Origin: https://自分のサイト を返す。JSONのPOSTなどは OPTIONS(事前確認)にも Access-Control-Allow-Methods / Allow-Headers を返す
同じオリジンにする 開発中は Vite などの開発サーバーのプロキシ機能で /api を転送する
サーバー経由にする 自分のサーバー(Node など)からAPIを呼ぶ。サーバー間通信にCORSは関係しない

mode: 'no-cors' は「エラーを消す設定」ではなく「中身を読めない opaque レスポンスを受け取る設定」なので、データ取得には使えません。


fetchとaxiosの違い

axios 1.20.0 で同じAPIを呼んだ結果です。

import axios from 'axios';
const r = await axios.get('http://localhost:7150/api/items', { params: { q: 'ノート', page: 2 } });
console.log(r.status, r.data.query);
try { await axios.get('http://localhost:7150/api/missing'); }
catch (e) { console.log(e.name, e.message, e.response.status); }
try { await axios.get('http://localhost:7150/api/slow?ms=3000', { timeout: 500 }); }
catch (e) { console.log(e.code, e.message); }
200 { q: 'ノート', page: '2' }
AxiosError Request failed with status code 404 404
ECONNABORTED timeout of 500ms exceeded
項目 fetch axios
導入 不要(ブラウザ・Node 18+ 標準) npm install axios
404/500 のとき 例外にならない(res.ok で判定) 例外になる(e.response.status)
JSONの取り出し await res.json() 自動で r.data
JSONの送信 JSON.stringify と Content-Type を自分で オブジェクトを渡せば自動
クエリ文字列 URLSearchParams params オプション
タイムアウト signal: AbortSignal.timeout(ms) timeout: ms
キャンセル AbortController signal(AbortController 対応)
共通処理(インターセプター) 関数でラップして作る interceptors が標準
ダウンロード進捗 res.body を読んで計算 onDownloadProgress
アップロード進捗 標準の方法なし onUploadProgress
ストリーミング res.body(ReadableStream)が標準 環境により設定が必要

依存を増やさず小さく書くなら fetch、共通のエラー処理やアップロード進捗をまとめて使いたいなら axios が選びやすい基準です。fetch でも、この記事の getJSON のような薄いラッパーを1つ用意すれば、多くの場面で同じように書けます。


よくある質問

Q. fetchで404なのに catch に入らないのはなぜですか? A. fetchは「レスポンスが返ってきた」時点で成功扱いにし、HTTPのステータスでは失敗にしない仕様だからです。if (!res.ok) throw new Error(...) を入れて、自分で例外にします。実行結果でも、404 のとき res.ok は false、res.status は 404 でした。

Q. Node.jsでfetchを使うのにライブラリは必要ですか? A. Node.js 18 以降は標準で使えます。 この記事の検証も Node v22.22.2 で追加パッケージなしに実行しました。ブラウザとの違いは、CORSの制限がないこと、Cookieを自動で保存しないこと、エラーの詳細が err.cause に入ることなどです。

Q. タイムアウトを設定するにはどうすればいいですか? A. fetch(url, { signal: AbortSignal.timeout(5000) }) のように書きます。 時間切れのときは err.name が TimeoutError になります。キャンセルボタンと両立したい場合は AbortSignal.any([controller.signal, AbortSignal.timeout(5000)]) を使います。

Q. Failed to fetch と出たときは何を確認すればいいですか? A. ブラウザでは、接続できない場合もCORSで拒否された場合も同じ TypeError: Failed to fetch になります。開発者ツールのコンソールに blocked by CORS policy が出ていればCORS、ERR_CONNECTION_REFUSED ならサーバーが起動していないかURLの誤り、と見分けられます。


まとめ

  • fetchは await fetch() と await res.json() の2段階。Node 18 以降とブラウザで同じコードが動く。
  • 404/500 は例外にならないので res.ok を必ず確認し、ラッパー関数(getJSON)にまとめる。
  • 本文は1回しか読めない。2回読むなら res.clone()。
  • タイムアウトは AbortSignal.timeout、キャンセルと両立するなら AbortSignal.any。判定は err.name で。
  • FormData では Content-Type を書かない。クエリ文字列は URLSearchParams に任せる。
  • 再試行は通信エラー・5xx・429 だけ、指数バックオフで。並列は Promise.all/失敗も集めるなら allSettled。
  • CORSはブラウザだけの仕組みで、対処はサーバー側。理由はコンソールに出る。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JavaScript fetchの使い方|用途別コピペ関数と実行結果」

株式会社RJCでは、APIとの通信のような毎日使う技術も、実際に動かして確かめた知見をプロジェクトの仲間と共有しています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?