При работе с fetch важно понимать: “ошибка” бывает разной. Иногда запрос успешно дошел до сервера, но сервер вернул 404. Иногда сеть недоступна. Иногда сервер вернул не JSON, а HTML, и парсер JSON упал.
Надежный код должен различать эти ситуации.
1) Важно: fetch и HTTP-ошибки
fetch не бросает ошибку только из-за статуса 404 или 500. В этих случаях Promise обычно успешно выполняется и возвращает Response, но response.ok будет false.
async function run() {
const response = await fetch("/api/missing");
console.log(response.ok);
console.log(response.status);
}
run();
Значит, статус нужно проверять вручную.
2) Классическая обработка: ok + json + try/catch
async function loadJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
const data = await response.json();
return data;
}
async function run() {
try {
const data = await loadJSON("/api/data");
console.log(data);
} catch (e) {
console.log(e.name);
console.log(e.message);
}
}
run();
Это базовый шаблон, но он не объясняет, что именно пошло не так. Дальше - типовые виды ошибок.
3) Ошибка статуса (404, 500) - это “ответ сервера”
Ситуация: сервер ответил, но с проблемой. Это не сетевой сбой, это корректный HTTP-ответ.
async function loadJSON(url) {
const response = await fetch(url);
if (!response.ok) {
const message = "Request failed: HTTP " + response.status;
throw new Error(message);
}
return response.json();
}
Часто полезно различать диапазоны:
- 400-499 - проблема в запросе (неверный URL, нет доступа)
- 500-599 - проблема на сервере
4) Сетевая ошибка: нет интернета, DNS, соединение
Сетевая ошибка - это когда запроса “как ответа” не получилось. В этом случае fetch отклоняет Promise, и код попадает в catch.
async function run() {
try {
await fetch("https://example.invalid");
} catch (e) {
console.log(e.name);
console.log(e.message);
}
}
run();
Точный текст message зависит от браузера.
5) AbortError: запрос был отменен
Запрос можно отменить через AbortController (например, таймаут или отмена при смене страницы).
async function run() {
const controller = new AbortController();
setTimeout(() => controller.abort(), 200);
try {
await fetch("/api/data", { signal: controller.signal });
} catch (e) {
console.log(e.name); // "AbortError" (обычно)
console.log(e.message);
}
}
run();
6) Неправильный JSON: сервер вернул не JSON
Частая причина: сервер вернул HTML-страницу ошибки или текст, а код делает response.json().
async function run() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
const data = await response.json();
console.log(data);
} catch (e) {
console.log(e.name);
console.log(e.message);
}
}
run();
Если JSON невалиден, ошибка будет на этапе response.json().
Полезная проверка: Content-Type
Иногда удобно проверить заголовок:
async function loadJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
const type = response.headers.get("content-type") || "";
if (!type.includes("application/json")) {
throw new Error("Expected JSON, got: " + type);
}
return response.json();
}
7) CORS: запрос запрещен браузером
Если запрос идет на другой домен, браузер может заблокировать его по CORS. Это выглядит как ошибка в fetch, хотя проблема не в коде.
Признак: запрос не дает доступа к данным, и в консоли DevTools есть сообщение про CORS.
Кодом на фронтенде это обычно не лечится - нужен корректный CORS на сервере.
8) Универсальная функция: различать причины
Ниже пример, который дает более понятные сообщения. Он не “магический”, но помогает привести ошибки к одному формату.
async function loadJSON(url, options) {
let response;
try {
response = await fetch(url, options);
} catch (e) {
throw new Error("Network error");
}
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
const type = response.headers.get("content-type") || "";
if (!type.includes("application/json")) {
throw new Error("Bad JSON content-type");
}
try {
return await response.json();
} catch (e) {
throw new Error("Invalid JSON");
}
}
async function run() {
try {
const data = await loadJSON("/api/data");
console.log(data);
} catch (e) {
console.log(e.message);
}
}
run();
Что стоит запомнить
- HTTP 404/500 - это не ошибка fetch, это ответ сервера, проверяется через response.ok и response.status.
- Сетевые ошибки попадают в catch до Response.
- Неправильный JSON ломается на response.json().
- AbortController дает AbortError при отмене.
- CORS выглядит как ошибка запроса, но решается на стороне сервера.
- Надежный шаблон: try function fetch(input, init = undefined) { // eslint-disable-line func-name-matching if (!fetchImpl) { // Implement lazy loading of undici module for fetch function const undiciModule = require('internal/deps/undici/undici'); fetchImpl = undiciModule.fetch; } return fetchImpl(input, init); } -> ok/status -> content-type -> json parse -> catch.