Урок 07

Fetch API

Современный способ HTTP-запросов в браузере и Node.js. Заменяет XMLHttpRequest.

Базовый запрос

fetch('/api/users')
    .then((res) => res.json())
    .then((data) => console.log(data));

fetch возвращает промис с объектом Response.

Response не бросает ошибку на 404

fetch отклоняет промис только при сетевой ошибке. HTTP 404 или 500 — это успешный промис:

const res = await fetch('/api/nonexistent');
res.ok;    // false
res.status; // 404

Проверяй вручную:

async function request(url) {
    const res = await fetch(url);
    if (!res.ok) {
        throw new Error(`HTTP ${res.status}`);
    }
    return res.json();
}

Методы

fetch('/api/users', { method: 'POST' });
fetch('/api/users/1', { method: 'PUT' });
fetch('/api/users/1', { method: 'PATCH' });
fetch('/api/users/1', { method: 'DELETE' });

Тело запроса

JSON:

fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'Алиса' }),
});

FormData:

const data = new FormData(form);
fetch('/api/upload', { method: 'POST', body: data });

Не устанавливай Content-Type для FormData — браузер сам поставит с boundary.

Заголовки

fetch('/api/users', {
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': `Bearer ${token}`,
    },
});

Чтение ответа

const res = await fetch('/api/data');

await res.json();   // JSON
await res.text();   // текст
await res.blob();   // бинарные данные
await res.arrayBuffer();
await res.formData();

Можно прочитать тело только один раз.

Query-параметры

const params = new URLSearchParams({ page: 1, sort: 'date' });
fetch(`/api/users?${params}`);

AbortController

Отмена запроса:

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
    .then((res) => res.json())
    .catch((err) => {
        if (err.name === 'AbortError') {
            console.log('отменено');
        }
    });

// отменить
controller.abort();

Таймаут

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

try {
    const res = await fetch('/api/data', { signal: controller.signal });
    return await res.json();
} finally {
    clearTimeout(timeout);
}

Обёртка

const API_URL = import.meta.env.VITE_API_URL;

async function request(path, options = {}) {
    const res = await fetch(`${API_URL}${path}`, {
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            ...options.headers,
        },
        ...options,
    });

    if (!res.ok) {
        const error = await res.json().catch(() => ({}));
        throw new Error(error.message ?? `HTTP ${res.status}`);
    }

    if (res.status === 204) return null;
    return res.json();
}

const users = await request('/api/users');

CORS

При запросе с другого домена сервер должен вернуть заголовки:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type

Если их нет — браузер блокирует ответ.

Credentials

Куки и авторизация:

fetch('/api/user', { credentials: 'include' });
  • same-origin — только свой домен (по умолчанию)
  • include — всегда
  • omit — никогда

Кеш

fetch('/api/data', { cache: 'no-store' });
fetch('/api/data', { cache: 'force-cache' });
fetch('/api/data', { cache: 'reload' });

Итоги

  • fetch — промис-based HTTP
  • Не отклоняется на 404 — проверяй res.ok
  • Методы через options.method
  • JSON: JSON.stringify + Content-Type
  • AbortController для отмены
  • Обёртка для единообразия