Чтобы получить данные с сервера или отправить форму без перезагрузки страницы, в JavaScript есть встроенная функция fetch. Она работает во всех современных браузерах и в Node.js без дополнительных библиотек. Ниже - как ей пользоваться, чем может быть удобнее библиотека Axios и что делать со старым кодом на XMLHttpRequest и jQuery.

fetch: GET-запрос

js
const response = await fetch('https://api.example.com/users');
const users = await response.json();
console.log(users);

fetch возвращает промис с объектом ответа. Тело ответа читается отдельно, тоже асинхронно:

  • response.json() - разобрать JSON;
  • response.text() - получить текст;
  • response.blob() - получить файл (например, картинку);
  • response.formData(), response.arrayBuffer() - для особых случаев.

await работает внутри функций, объявленных как async, и на верхнем уровне модулей. Без async/await тот же код пишется через then:

js
fetch('https://api.example.com/users')
    .then(response => response.json())
    .then(users => console.log(users));

Параметры запроса удобно собирать через URLSearchParams - он сам экранирует значения:

js
const params = new URLSearchParams({ q: 'ноутбук', page: 2 });
const response = await fetch(`https://api.example.com/search?${params}`);

Обработка ошибок

Три карточки ответа: отметка, восклицательный знак и разорванная ссылка, щит у браузера

Важная особенность fetch: промис отклоняется только при сетевой ошибке (нет соединения, сервер недоступен, запрос заблокирован политикой CORS). Ответы с кодами 404 или 500 считаются успешными - проверять код нужно самому:

js
async function getJson(url) {
    const response = await fetch(url);

    if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return response.json();
}

try {
    const data = await getJson('https://api.example.com/users/42');
    console.log(data);
} catch (error) {
    console.error('Не удалось загрузить данные:', error.message);
}

response.ok равен true для кодов 200-299. Сам код - в response.status.

POST-запрос с JSON

js
const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({ name: 'Иван', email: 'ivan@example.com' }),
});

const created = await response.json();

Объект нужно превратить в строку через JSON.stringify и указать заголовок Content-Type, иначе сервер не поймёт формат.

Методы PUT, PATCH и DELETE отправляются так же, меняется только method.

Отправка формы и файлов

FormData собирает поля формы, включая файлы:

js
const form = document.querySelector('#upload-form');

form.addEventListener('submit', async (event) => {
    event.preventDefault();

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

    if (response.ok) {
        alert('Файл загружен');
    }
});

Заголовок Content-Type для FormData указывать не нужно: браузер сам поставит multipart/form-data с правильной границей частей.

Заголовки, куки и авторизация

js
const response = await fetch('https://api.example.com/profile', {
    headers: {
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/json',
    },
    credentials: 'include',
});

credentials: 'include' отправляет куки и на другой домен (по умолчанию куки отправляются только на свой). Заголовки ответа читаются через response.headers.get('Content-Type').

Тайм-аут и отмена запроса

Линия запроса, крестик, секундомер и сервер

У fetch нет встроенного тайм-аута, но есть сигнал отмены:

js
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });

Через 5 секунд запрос будет прерван с ошибкой TimeoutError. Отменить запрос вручную, например, когда пользователь ушёл со страницы или ввёл новый поисковый запрос:

js
const controller = new AbortController();

fetch(url, { signal: controller.signal })
    .then(response => response.json())
    .catch(error => {
        if (error.name === 'AbortError') {
            console.log('Запрос отменён');
        }
    });

controller.abort();

Несколько запросов параллельно

js
const [users, orders] = await Promise.all([
    getJson('/api/users'),
    getJson('/api/orders'),
]);

Promise.all завершится ошибкой, если упадёт хотя бы один запрос. Если нужны результаты всех, даже при частичных ошибках, используйте Promise.allSettled.

CORS

Если страница на одном домене обращается к API на другом, браузер применяет политику CORS: запрос пройдёт, только если сервер разрешил это заголовком Access-Control-Allow-Origin. Ошибка «blocked by CORS policy» в консоли означает, что настраивать нужно сервер, а не клиентский код. Параметр mode: 'no-cors' проблему не решает: запрос уйдёт, но прочитать ответ будет нельзя.

Axios

Axios - популярная библиотека для HTTP-запросов. Она работает в браузере и Node.js и делает некоторые вещи удобнее:

  • JSON разбирается и отправляется автоматически;
  • ответы с кодами ошибок (4xx, 5xx) сразу отклоняют промис;
  • есть встроенный тайм-аут и перехватчики (interceptors) для общих заголовков и обработки ошибок;
  • можно создать экземпляр с базовым адресом и общими настройками.
bash
npm install axios
js
import axios from 'axios';

const api = axios.create({
    baseURL: 'https://api.example.com',
    timeout: 5000,
});

api.interceptors.request.use(config => {
    config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
    return config;
});

try {
    const { data } = await api.get('/users', { params: { page: 2 } });
    await api.post('/users', { name: 'Иван' });
} catch (error) {
    console.error(error.response?.status, error.message);
}

Для небольших проектов достаточно fetch и маленькой обёртки вроде getJson выше. Axios оправдан в приложениях с десятками запросов к одному API, где перехватчики и общие настройки экономят код.

XMLHttpRequest и jQuery

XMLHttpRequest - старый интерфейс, на котором построены прежние AJAX-библиотеки. В новом коде он не нужен, но встречается в старых проектах:

js
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.responseType = 'json';
xhr.onload = () => {
    if (xhr.status >= 200 && xhr.status < 300) {
        console.log(xhr.response);
    }
};
xhr.onerror = () => console.error('Сетевая ошибка');
xhr.send();

У него есть одно преимущество перед fetch: событие upload.onprogress, которое показывает прогресс загрузки файла на сервер. Если нужен индикатор загрузки, это до сих пор самый простой способ.

В jQuery запросы делаются через $.ajax, $.get и $.post. Если jQuery на сайте нужен только для запросов, его можно заменить на fetch:

js
// было
$.post('/api/users', { name: 'Иван' }, data => console.log(data));

// стало
fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({ name: 'Иван' }),
}).then(r => r.json()).then(data => console.log(data));

$.post по умолчанию отправляет данные как поля формы, а не как JSON, поэтому в замене используется URLSearchParams - так серверный код не придётся менять.

Node.js

В современных версиях Node.js fetch встроен и работает так же, как в браузере, включая AbortSignal.timeout. Ограничений CORS в Node.js нет: это защита браузера, а не сервера.