Использование внешних API

Fetch API предоставляет современный способ выполнения HTTP-запросов в JavaScript, заменяя устаревший XMLHttpRequest. Основной вызов функции fetch возвращает объект Promise, который позволяет работать с асинхронными операциями через цепочки .then() или с использованием async/await.

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Ошибка запроса:', error));

Ключевые моменты:

  • fetch по умолчанию выполняет GET-запрос.
  • Объект Response предоставляет методы .json(), .text(), .blob() для обработки ответа.
  • Ошибки сети (например, отсутствие соединения) обрабатываются через .catch(), но HTTP-статусы 4xx и 5xx не считаются ошибкой, их нужно проверять вручную.
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error(error));

Асинхронные функции с fetch

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

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

getData();

Особенности:

  • await приостанавливает выполнение функции до получения результата Promise.
  • Обработка ошибок через try/catch упрощает контроль над исключениями.
  • Можно комбинировать несколько запросов параллельно с помощью Promise.all() для повышения производительности.
async function getMultipleData() {
  try {
    const [users, posts] = await Promise.all([
      fetch('https://api.example.com/users').then(res => res.json()),
      fetch('https://api.example.com/posts').then(res => res.json())
    ]);
    console.log(users, posts);
  } catch (error) {
    console.error(error);
  }
}

Настройка запросов

fetch позволяет детально настраивать запрос через объект настроек:

fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer TOKEN'
  },
  body: JSON.stringify({ name: 'John', age: 30 }),
  mode: 'cors',
  cache: 'no-cache'
});

Основные опции:

  • method — HTTP-метод (GET, POST, PUT, DELETE).
  • headers — объект заголовков, включая Content-Type и авторизационные токены.
  • body — данные запроса для POST/PUT. Для JSON нужно использовать JSON.stringify().
  • mode — режим CORS (cors, no-cors, same-origin).
  • credentials — контроль куки и авторизации (omit, same-origin, include).

Работа с CORS и ограничениями браузера

Cross-Origin Resource Sharing (CORS) ограничивает доступ к ресурсам другого домена. Сервер должен отправлять заголовок Access-Control-Allow-Origin, иначе браузер заблокирует ответ.

fetch('https://api.otherdomain.com/data', { mode: 'cors' })
  .then(res => res.json())
  .catch(err => console.error('Ошибка CORS или сети', err));

Особенности:

  • no-cors позволяет отправлять запрос, но ограничивает доступ к телу ответа.
  • Для полноценного взаимодействия требуется поддержка CORS на сервере.
  • Опция credentials: 'include' позволяет отправлять куки при междоменных запросах.

Обработка ошибок и таймауты

Fetch не поддерживает таймаут напрямую. Таймаут можно реализовать через AbortController:

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

fetch('https://api.example.com/data', { signal: controller.signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.error('Запрос прерван из-за таймаута');
    } else {
      console.error('Ошибка запроса', err);
    }
  })
  .finally(() => clearTimeout(timeoutId));

Ключевые моменты:

  • AbortController позволяет прервать запрос вручную.
  • Таймауты полезны для предотвращения зависания при медленных соединениях.

Работа с потоками данных

Fetch поддерживает работу с потоками через ReadableStream. Это позволяет обрабатывать большие данные без полной загрузки в память:

fetch('https://api.example.com/large-file')
  .then(response => {
    const reader = response.body.getReader();
    return new ReadableStream({
      start(controller) {
        function push() {
          reader.read().then(({ done, value }) => {
            if (done) {
              controller.close();
              return;
            }
            controller.enqueue(value);
            push();
          });
        }
        push();
      }
    });
  })
  .then(stream => new Response(stream))
  .then(res => res.text())
  .then(text => console.log(text));

Особенности:

  • Потоки позволяют обрабатывать большие файлы частями.
  • Комбинирование с TextDecoder позволяет декодировать бинарные данные в текст по мере поступления.

Использование fetch для JSON API

Наиболее распространённый сценарий — работа с JSON API. Часто используется сочетание async/await и проверок статуса:

async function fetchJson(url) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Ошибка HTTP: ${response.status}`);
  }
  return response.json();
}

(async () => {
  try {
    const data = await fetchJson('https://api.example.com/data');
    console.log(data);
  } catch (err) {
    console.error(err);
  }
})();

Практические рекомендации:

  • Всегда проверять response.ok для надёжной обработки ошибок.
  • Использовать централизованную функцию для запросов к API, чтобы избежать дублирования кода.
  • Поддерживать обработку ошибок сети и таймаутов, чтобы приложение оставалось устойчивым.

Параллельные и последовательные запросы

Для повышения эффективности можно выполнять несколько запросов параллельно с помощью Promise.all, или последовательно при зависимостях данных:

// Параллельные запросы
const [users, posts] = await Promise.all([
  fetchJson('/users'),
  fetchJson('/posts')
]);

// Последовательные запросы
const user = await fetchJson('/users/1');
const posts = await fetchJson(`/posts?userId=${user.id}`);

Отличие подходов:

  • Параллельные запросы ускоряют загрузку, если данные независимы.
  • Последовательные запросы необходимы, если результат одного запроса используется в следующем.

Заголовки и аутентификация

При работе с API часто требуется авторизация. Заголовки передаются через объект headers:

fetch('https://api.example.com/protected', {
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Accept': 'application/json'
  }
})
  .then(res => res.json())
  .then(data => console.log(data));

Особенности:

  • Bearer используется для токенов OAuth.
  • Basic подходит для базовой HTTP-аутентификации.
  • Настройка заголовков Content-Type важна для правильной отправки данных.

Обработка форм и multipart-запросов

Для загрузки файлов или отправки форм используется FormData:

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'John');

fetch('https://api.example.com/upload', {
  method: 'POST',
  body: formData
})
  .then(res => res.json())
  .then(data => console.log(data));

Особенности:

  • FormData автоматически устанавливает заголовок Content-Type с границей multipart.
  • Подходит для отправки файлов, изображений и сложных форм.

Работа с кэшированием

Fetch поддерживает управление кэшированием через опцию cache:

  • default — стандартное поведение браузера.
  • no-cache — проверяет сервер перед использованием кэшированных данных.
  • reload — всегда загружает заново.
  • force-cache — использует только кэш, игнорируя сервер.
  • only-if-cached — возвращает только кэшированные данные, иначе ошибка.
fetch('/data', { cache: 'no-cache' });

Эта функциональность полезна для контроля свежести данных и оптимизации производительности.