Скидка до 55% и 3 курса в подарок 2 дня 13 :30 :09 Выбрать курс
Код Справочник по фронтенду
#статьи

Что такое функция fetch() в JavaScript и как её использовать

Делаем первый запрос и собираем галерею с собачками.

Иллюстрация: Polina Vari для Skillbox Media

Большинство современных веб-приложений работает с данными, которые хранятся на удалённом сервере. Например, когда вы открываете ленту в социальной сети, браузер отправляет HTTP-запрос и загружает посты и изображения с сервера компании, а не из локального файла на компьютере.

Загрузка данных с сервера занимает время — иногда несколько секунд. Если бы браузер просто ждал ответа и ничего не делал, страница бы зависала и пользователь не мог бы с ней взаимодействовать. Поэтому обычно все запросы выполняются асинхронно — в фоновом режиме, не блокируя работу страницы.

Для асинхронного обмена данными с сервером используется технология AJAX, которую мы разбирали в отдельном материале. Разработчики реализуют её разными способами — от устаревшего браузерного объекта XMLHttpRequest до современных библиотек вроде Axios. Одним из самых популярных решений стала встроенная функция fetch() в JavaScript, которая не требует установки и работает во всех современных браузерах. Именно с fetch() мы познакомимся в этой статье: разберём синтаксис, сделаем первый запрос и научимся обрабатывать ответы.

Содержание


Изучаем синтаксис функции fetch()

Когда разработчики только начинали создавать первые веб-приложения, единственным стандартным способом отправки HTTP-запросов из браузера был объект XMLHttpRequest (XHR), который впервые появился в Internet Explorer 5 в 1999 году, а затем был стандартизирован и добавлен в другие браузеры.

Хотя XHR работает надёжно, по современным меркам он выглядит громоздким. Чтобы сделать простой запрос, нужно вручную открывать соединение, отслеживать изменение состояний и прописывать несколько обработчиков. Ошибки обрабатываются неочевидно, тело ответа нужно разбирать самостоятельно, и код быстро обрастает повторяющимися фрагментами.

Чтобы решить эти проблемы, в браузерах появилась функция fetch() — встроенный инструмент для работы с HTTP-запросами. Она выполняет ту же задачу, что и XMLHttpRequest, но код получается короче, структура — понятнее, а работа с данными — естественнее. Базовый синтаксис fetch() выглядит так:

fetch(url, [options])

В этой записи url — строка с адресом ресурса, к которому мы обращаемся. Это обязательный параметр, поскольку без него функция не сможет определить, куда отправлять запрос, и выдаст ошибку. Параметр options — необязательный объект, с помощью которого вы можете указать HTTP-метод, заголовки, тело запроса и другие параметры. Если options не указать, функция fetch() автоматически выполнит простой GET-запрос — запросит данные с сервера.

Мы понимаем, что без практики довольно сложно понять работу fetch(). Поэтому для обучения предлагаем использовать специальные сервисы, которые предоставляют бесплатные данные для HTTP-запросов. Один из таких сервисов — Dog API, который возвращает случайные фотографии собак в формате JSON. Он не требует регистрации и отлично подходит для первых экспериментов.

Откройте главную страницу Dog API и нажмите кнопку Fetch! — вы сразу увидите ответ: объект в формате JSON и изображение собаки из общей коллекции фото.

Скриншот: Dog API / Skillbox Media

Теперь давайте попробуем сделать самый простой запрос. Прямо на с

fetch('https://dog.ceo/api/breeds/image/random')

Когда вы нажмёте Enter, в консоли появится объект Promise со статусом fulfilled (выполнен). Это означает, что функция fetch() отправила запрос и получила ответ от сервера, но сами данные пока находятся внутри промиса в необработанном виде. Их обработкой мы займёмся в следующем разделе.

Однако иногда вместо промиса вы можете получить ошибку Access to fetch has been blocked by CORS policy. Убедитесь, что выполняете код именно на сайте Dog API. Если попытаетесь выполнить его на пустой странице или любом другом сайте, браузер заблокирует запрос из-за политики CORS (Cross-Origin Resource Sharing) — механизма безопасности, который запрещает браузеру отправлять запросы к внешним доменам без разрешения сервера. Чтобы запрос работал, он должен идти с того же домена, на котором находится страница.

Скриншот: Dog API / Skillbox Media

Извлекаем данные из промиса после отработки fetch()

Promise (промис) — это специальный объект в JavaScript, который как бы говорит: «Я выполняю задачу, но результат будет позже». Когда вы пишете fetch(), браузер сразу даёт вам промис и продолжает работать дальше, не ожидая ответа от сервера. Благодаря этому страница не зависает и остаётся рабочей.

Промис может находиться в одном из трёх состояний:

  • pending — операция ещё выполняется;
  • fulfilled — операция завершилась успешно;
  • rejected — произошла ошибка.

В предыдущем разделе функция fetch() вернула промис со статусом fulfilled, внутри которого находится объект ответа response. Этот объект содержит только метаинформацию — статус, заголовки и поток с данными. Чтобы получить сами данные, необходимо применить метод .then() или синтаксис async/await. Оба подхода работают одинаково, однако отличаются удобством чтения: .then() хорош для коротких цепочек и обработки нескольких независимых запросов, а async/await проще просматривать при последовательных операциях.

На практике разработчики чаще используют async/await, поскольку такой код легче разбирать и отлаживать. Но для понимания мы рассмотрим оба варианта.

Используем цепочку .then()

.then() — это метод промиса, который выполняет переданную функцию после того, как промис перешёл в состояние fulfilled. Методы .then() можно записывать цепочкой, чтобы каждое следующее звено получало результат предыдущего, обрабатывало его, а затем возвращало готовое значение или новый промис для дальнейшей асинхронной работы. Это позволяет последовательно выполнять операции без глубокой вложенности колбэков.

Также стоит учитывать, что функция fetch() сама по себе не считает HTTP-ошибки причиной для отклонения промиса. Даже если сервер вернёт статус 404 (страница не найдена) или 500 (ошибка сервера), промис всё равно перейдёт в состояние fulfilled. Вы получите объект response с кодом ошибки в поле status. Промис переходит в состояние rejected только при сетевом сбое — когда сервер недоступен, нет интернета или запрос заблокирован браузером.

Поэтому в коде нужно проверять свойство response.ok или код статуса response.status — так вы отличите успешные запросы от ошибок сервера. Если проверка показала ошибку, выбросьте исключение через throw new Error() — это переведёт промис в состояние rejected. Для обработки ошибок сервера и сетевых сбоев в цепочке .then() нужен метод .catch(). Он позволяет приложению стабильно работать, а пользователю — получать понятные сообщения о проблемах.

Получим фотографию собаки из Dog API:

fetch('https://dog.ceo/api/breeds/image/random')
  .then(response => {
    if (!response.ok) {
      throw new Error('Ошибка HTTP: ' + response.status);
    }
    return response.json();
  })
  .then(data => {
    console.log('URL-адрес картинки:', data.message);
  })
  .catch(error => {
    console.error('Произошла ошибка:', error);
  });

В этом коде у нас образовалась цепочка из двух методов .then(). Первый .then() получает объект response — результат выполнения fetch(). Мы проверяем свойство response.ok (оно равно true, если статус в диапазоне 200–299) и, если всё в порядке, вызываем метод response.json(). Этот метод читает тело ответа в формате JSON и возвращает новый промис. Второй .then() получает уже распарсенный объект data с полем message, в котором Dog API возвращает URL-адрес картинки с собакой. Следующим идёт блок .catch(), который перехватывает все возможные ошибки: сетевые и те, что мы выбросили сами.

Скриншот: Dog API / Skillbox Media

Применяем async/await

Синтаксис async/await — это современный способ работы с промисами, который делает асинхронный код похожим на обычный последовательный. Когда вы объявляете функцию с ключевым словом async, она автоматически возвращает промис. Внутри такой функции можно использовать ключевое слово await, которое приостанавливает выполнение до завершения промиса.

По сути, async/await решает те же задачи, что и .then(), но с более читаемым синтаксисом. Вместо цепочки методов вы пишете последовательный код, а для обработки ошибок используете привычную конструкцию try…catch вместо .catch(). Давайте перепишем предыдущий пример с цепочкой .then():

const loadDogImage = async() => {
  try {
    const response = await fetch('https://dog.ceo/api/breeds/image/random');
    
    if (!response.ok) {
      throw new Error('Ошибка HTTP: ' + response.status);
    }
    
    const data = await response.json();
    console.log('URL-адрес картинки:', data.message);
  } catch (error) {
    console.error('Произошла ошибка:', error);
  }
};

loadDogImage();

Сначала с помощью await fetch() мы дожидаемся ответа от сервера и сохраняем его в переменную response. Затем проверяем статус запроса через response.ok. После этого с помощью await response.json() извлекаем и парсим JSON-данные в объект data и выводим URL картинки в консоль. Все возможные ошибки перехватываются в блоке catch — как в обычном синхронном коде.

Скриншот: Dog API / Skillbox Media

Обратите внимание: во всех предыдущих примерах мы использовали метод response.json(), потому что большинство современных API возвращают данные в JSON. Однако fetch() поддерживает и другие форматы. К примеру, метод .text() подходит для получения текста (HTML, XML, CSV), .blob() — для бинарных файлов (изображения, видео, документы), .formData() — для данных форм с файлами, а .arrayBuffer() — для низкоуровневой работы с байтами.

Все эти методы работают асинхронно и возвращают промисы, поэтому их можно использовать с .then() или async/await — точно так же, как с JSON. Выбор метода зависит от формата данных, который отправляет сервер. Узнать его можно из документации API или проверив заголовок Content-Type в ответе.

Используем параметр options для отправки данных

До этого мы использовали fetch() без второго параметра, и функция автоматически выполняла простой GET-запрос. Второй параметр — это объект options, который позволяет гибко настраивать запросы: менять HTTP-метод, добавлять заголовки для аутентификации или указания формата данных, передавать тело запроса с данными, настраивать кеширование и режим CORS.

Базовая структура функции fetch() с объектом options выглядит так:

fetch(url, {
  method: 'POST',           // HTTP-метод
  headers: {                // Заголовки запроса
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({   // Тело запроса
    key: 'value'
  })
})

Если вы хотите попробовать написать функцию fetch() с параметром options, то сервиса Dog API недостаточно. Он предоставляет доступ к базе данных изображений собак, но не позволяет ничего изменять. Однако есть сервис JSONPlaceholder — он эмулирует полноценный REST API и поддерживает все основные HTTP-методы. Перейдите на его главную страницу и откройте консоль браузера. Напишем полный цикл POST-запроса с обработкой ответа:

const createPost = async() => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        title: 'Моя любимая порода',
        body: 'Тибетский спаниель — отличные собаки!',
        userId: 1
      })
    });

    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }

    const data = await response.json();
    console.log('Создан пост с ID:', data.id);
    console.log('Полный ответ:', data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
};

createPost();

После этого в консоли появятся два сообщения. Первое покажет ID созданного поста — 101. Это означает, что запрос выполнен успешно и сервер подтвердил создание ресурса. Второе выведет полный объект ответа с отправленными данными (title, body, userId) плюс автоматически присвоенный идентификатор. При этом важно понимать, что JSONPlaceholder — это тестовый API, который имитирует создание постов, но не сохраняет их в реальной базе. Поэтому для всех новых POST-запросов он возвращает один и тот же ID — 101.

Скриншот: JSONPlaceholder / Skillbox Media

Теперь сравним GET и POST запросы, чтобы увидеть разницу:

// GET-запрос — получаем существующий пост
const getPost = async() => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
    
    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }
    
    const data = await response.json();
    console.log('Получен пост:', data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
};

// POST-запрос — создаём новый пост
const createPost = async() => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        title: 'Новый пост',
        body: 'Какое-то содержание поста',
        userId: 1
      })
    });
    
    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }
    
    const data = await response.json();
    console.log('Создан пост:', data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
};

// Запускаем оба запроса
getPost();
createPost();

Первый запрос получает данные с сервера и не требует параметров options — достаточно указать URL. Второй запрос отправляет новые данные и обязательно использует method, headers и body. GET-запрос используется для чтения, POST — для создания записей. Именно поэтому в нашей галерее собак мы обходились без options: Dog API поддерживает только чтение данных через GET-запросы.

Скриншот: JSONPlaceholder / Skillbox Media

Помимо POST, существуют и другие HTTP-методы. PUT полностью заменяет существующий ресурс новыми данными — все поля перезаписываются, даже если вы не указали некоторые из них в запросе. PATCH работает иначе: он изменяет только те поля, которые вы явно указали, оставляя остальные без изменений. DELETE удаляет ресурс с сервера по указанному идентификатору.

Для примера попробуем обновить пост с помощью метода PUT:

const updatePost = async() => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        id: 1,
        title: 'Обновлённый заголовок',
        body: 'Обновлённое содержание поста',
        userId: 1
      })
    });

    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }

    const data = await response.json();
    console.log('Обновлён пост:', data);
  } catch (error) {
    console.error('Ошибка:', error);
  }
};

updatePost();

Мы заменили весь пост с ID 1 новыми данными, и вот результат в консоли.

Скриншот: JSONPlaceholder / Skillbox Media

Параметр options поддерживает и другие настройки:

  • Свойство mode управляет политикой CORS: cors разрешает кросс-доменные запросы с полной проверкой заголовков и доступом к ответу, no-cors включает ограниченный режим для запросов к сторонним ресурсам без возможности чтения ответа, а same-origin позволяет выполнять запросы только в пределах того же домена, блокируя кросс-доменные обращения.
  • Свойство cache определяет стратегию кеширования: default использует стандартные правила браузера, no-cache всегда проверяет актуальность данных на сервере перед использованием кеша, reload полностью игнорирует кеш и всегда загружает свежие данные напрямую с сервера.
  • Свойство credentials управляет отправкой cookies и заголовков аутентификации: omit не отправляет их, same-origin отправляет только для запросов в пределах одного домена, include отправляет всегда — даже для кросс-доменных запросов. Эти параметры используются не слишком часто, однако помогают точно настроить запросы в специфических сценариях.

Теперь, когда мы разобрались с параметром options, можем перейти к финальному проекту. В нём мы будем использовать только GET-запросы, поэтому объект options нам не понадобится — можно вернуться к сервису Dog API. Однако понимание работы с POST, PUT и другими методами пригодится при работе с API, которые позволяют создавать, изменять и удалять данные.

Читайте также:

Методы GET и POST в HTTP

Собираем галерею с асинхронной загрузкой изображений

Теперь давайте применим знания о функции fetch() на практике и создадим интерактивную галерею с изображениями собак. Наша галерея будет загружать случайные фотографии через Dog API с помощью асинхронных запросов, отображать их в виде карточек и обновляться по клику на кнопку. Также добавим возможность выбирать количество фото для одновременной загрузки.

Шаг 1. Для начала обернём весь код галереи в асинхронную функцию loadDogs(). Она получит контейнер для отображения галереи через getElementById(), посчитает количество фото из поля ввода (по умолчанию восемь), а затем выполнит все нужные операции. Ещё добавим конструкцию try…catch, чтобы при ошибке показать понятное сообщение вместо пустой страницы.

async function loadDogs() {
  const galleryContainer = document.getElementById('gallery');
  const countInput = document.getElementById('dogCount');
  const count = parseInt(countInput.value) || 8;
  
  // Показываем индикатор загрузки
  galleryContainer.innerHTML = `
    <div class="loading">
      <div class="spinner"></div>
      <p class="loading-text">Загружаем ${count} собак...</p>
    </div>
  `;
  
  try {
    // Шаги 25: загрузка и отображение данных
    
  } catch (error) {
    console.error('Ошибка при загрузке:', error);
    galleryContainer.innerHTML = `
      <div class="error">
        ❌ Произошла ошибка при загрузке изображений: ${error.message}
      </div>
    `;
  }
}

// Запускаем функцию при загрузке страницы
window.addEventListener('load', () => {
  loadDogs();
});

Шаг 2. Теперь получим список всех доступных пород собак через Dog API. Для этого отправим GET-запрос на эндпоинт https://dog.ceo/api/breeds/list/all. В ответ API вернёт JSON-объект, где каждый ключ — это название породы. После этого воспользуемся методом Object.keys(), чтобы извлечь эти ключи из объекта breedsData.message и преобразовать их в массив строк для дальнейшей работы.

const breedsResponse = await fetch('https://dog.ceo/api/breeds/list/all');
const breedsData = await breedsResponse.json();
const allBreeds = Object.keys(breedsData.message);

Шаг 3. Dog API возвращает около 190 пород собак, но нам не нужны все фотографии сразу. Поэтому выберем несколько случайных пород с помощью алгоритма Фишера — Йетса — он гарантирует равномерное распределение, где у каждой породы будет одинаковая вероятность попасть в выборку. После перемешивания оставляем нужное количество элементов методом slice().

const shuffleArray = (array) => {
  const shuffled = [...array];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  return shuffled;
};

const selectedBreeds = shuffleArray(allBreeds).slice(0, count);

Шаг 4. Для каждой выбранной породы загрузим случайное изображение. Применим метод .map(), который пройдётся по массиву пород и для каждой отправит отдельный запрос к Dog API. Затем с помощью функции Promise.all() дождёмся завершения всех запросов и соберём результаты в один массив. Это быстрее последовательной загрузки: если бы мы запрашивали фото по очереди, то при 120 породах ожидание могло бы составить несколько секунд. С параллельной загрузкой все изображения приходят почти одновременно.

const dogPromises = selectedBreeds.map(async (breed) => {
  try {
    const response = await fetch(`https://dog.ceo/api/breed/${breed}/images/random`);
    
    if (!response.ok) {
      throw new Error(`Ошибка HTTP: ${response.status}`);
    }
    
    const data = await response.json();
    return {
      breed, 
      imageUrl: data.message
    };
  } catch (error) {
    console.error(`Ошибка загрузки породы ${breed}:`, error);
    return null;
  }
});

const results = await Promise.all(dogPromises);
const dogs = results.filter(dog => dog !== null);

if (dogs.length === 0) {
  throw new Error('Не удалось загрузить ни одного изображения');
}

Шаг 5. Осталось отобразить загруженные данные на странице. С помощью метода .map() создадим HTML-карточку для каждой собаки с изображением и названием породы. Затем методом .join('') объединим все карточки в одну строку и вставим результат в контейнер через свойство innerHTML. Кроме того, дополнительно сделаем так, чтобы каждая карточка получила небольшую задержку анимации — это позволит элементам появляться на странице плавно и поочерёдно.

galleryContainer.innerHTML = `
  <div class="gallery">
    ${dogs.map((dog, index) => `
      <div class="dog-card" style="animation-delay: ${index * 0.1}s">
        <img src="${dog.imageUrl}" alt="Собака породы ${dog.breed.replace(/-/g, ' ')}">
        <div class="dog-overlay">
          <div class="dog-name">${dog.breed.replace(/-/g, ' ')}</div>
        </div>
      </div>
    `).join('')}
  </div>
`;

Чтобы галерея выглядела красиво, добавим стилизацию. У нас в редакторе установлена тёмная тема One Dark Pro — давайте её и используем. Ещё добавим в кнопку логотип Dog API и подключим файл Normalize.css, чтобы дефолтные браузерные стили нам никак не мешали. Для удобства соберём разметку, стили и логику приложения в один файл dog-gallery.html, который вы можете скачать и запустить. При каждом нажатии кнопки Fetch галерея будет очищаться и загружать новый набор случайных пород с их фотографиями. Также можно изменить количество картинок через поле ввода — от 1 до 190 карточек за раз.

Интерактивная галерея на чистом JavaScript с асинхронной загрузкой данных через fetch()
Скриншот: Skillbox Media

Больше интересного про код — в нашем телеграм-канале.  Подписывайтесь!






Курс с трудоустройством: «Профессия Фронтенд-разработчик + ИИ» Узнать о курсе
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована