React Front-end Инженер

React Front-end Инженер

Роадмап навыков для прокачки

Базовое использование fetch()

JavaScript (ES6 и новее)Сетевое взаимодействиеFetch API

Основная идея

Fetch API — современный интерфейс для выполнения HTTP-запросов в JavaScript, основанный на Promise. Заменяет устаревший XMLHttpRequest, предоставляя более чистый и удобный синтаксис.

Ключевые аспекты

  • Синтаксисfetch(url, options) возвращает Promise с объектом Response
  • Два этапа — сначала получаем Response, потом парсим тело (.json(), .text())
  • По умолчанию GET — без options выполняется GET-запрос
  • CORS — fetch соблюдает политику same-origin
  • Ошибки — HTTP ошибки (404, 500) НЕ вызывают reject Promise

Плюсы fetch

  • Простой и понятный API на основе Promise
  • Поддержка async/await из коробки
  • Встроен в современные браузеры
  • Гибкая настройка заголовков и тела запроса

Минусы

  • Не отклоняет Promise при HTTP ошибках (нужна проверка response.ok)
  • Нет встроенного таймаута
  • Нет прогресса загрузки (в отличие от XHR)
  • Требует полифилл для старых браузеров

Частые ошибки на собеседованиях

  • Забывают вызвать .json() или .text() для получения данных
  • Не проверяют response.ok или response.status
  • Путают — fetch не отклоняет Promise при 404/500
  • Забывают о двух этапах: Response → Body

Введение и проблематика

До появления Fetch API для HTTP-запросов использовался XMLHttpRequest (XHR) — громоздкий API с callback-based подходом:

Code Example 1: Что делает этот код? Какой подход к асинхронности здесь используется и какие у него недостатки?

js
// ❌ Старый XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  }
};
xhr.onerror = function() {
  console.error('Ошибка');
};
xhr.send();

Fetch API, появившийся в ES6, предоставляет современный Promise-based интерфейс для сетевых запросов.

Fetch — это глобальная функция, доступная в браузере и современных версиях Node.js (с v18+).


Базовая теория

Сигнатура функции

js
fetch(url, options?)
  • url — URL для запроса (строка или URL объект)
  • options — необязательный объект с настройками запроса
  • Возвращает Promise<Response>

Двухэтапный процесс

sequenceDiagram participant JS as JavaScript participant F as fetch() participant S as Сервер JS->>F: fetch(url) F->>S: HTTP запрос S-->>F: HTTP ответ (заголовки) F-->>JS: Promise<Response> JS->>F: response.json() F-->>JS: Promise<Data>

Практические примеры

Простой GET-запрос

Code Example 2: Оба варианта делают GET-запрос. Из скольких этапов состоит получение данных через fetch? Что происходит на каждом этапе?

js
// С использованием .then()
fetch('https://api.example.com/users')
  .then(response => {
    // Первый этап: получили Response
    console.log(response.status); // 200
    console.log(response.ok); // true
 
    // Второй этап: парсим тело
    return response.json();
  })
  .then(data => {
    // Получили данные
    console.log(data);
  })
  .catch(error => {
    // Только сетевые ошибки!
    console.error('Сетевая ошибка:', error);
  });

Методы чтения тела ответа

Code Example 3: Какие методы для чтения тела ответа показаны? Можно ли вызвать несколько из них подряд на одном response?

js
const response = await fetch('/api/data');
 
// JSON данные
const json = await response.json();
 
// Текст
const text = await response.text();
 
// Бинарные данные
const blob = await response.blob();
const buffer = await response.arrayBuffer();
 
// Form data
const formData = await response.formData();
⚠️

Важно: тело ответа можно прочитать только один раз! После вызова .json() повторный вызов выдаст ошибку.

POST-запрос с JSON

Code Example 4: Как здесь отправляется POST-запрос? Какие обязательные настройки нужны для отправки JSON?

js
async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData),
  });
 
  if (!response.ok) {
    throw new Error(`Ошибка: ${response.status}`);
  }
 
  return response.json();
}
 
// Использование
const newUser = await createUser({
  name: 'Иван',
  email: 'ivan@example.com'
});

Настройки запроса (options)

Code Example 5: Какие параметры можно передать в объекте options? Для чего нужны mode, credentials и cache?

js
fetch(url, {
  method: 'POST',          // GET, POST, PUT, DELETE, PATCH
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify(data), // тело запроса
  mode: 'cors',            // cors, no-cors, same-origin
  credentials: 'include',  // include, same-origin, omit
  cache: 'no-cache',       // default, no-cache, reload, force-cache
  redirect: 'follow',      // follow, error, manual
});

Пограничные кейсы

🚫

Критически важно: fetch НЕ отклоняет Promise при HTTP-ошибках!

Code Example 6: Чем отличается поведение этих двух вариантов при ответе сервера с кодом 404? Попадёт ли 404 в .catch() в первом варианте?

js
// ❌ Это НЕ попадёт в catch при 404 или 500!
fetch('/api/not-found')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error)); // Не сработает для 404!
 
// ✅ Правильная обработка HTTP ошибок
fetch('/api/not-found')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error(error)); // Теперь поймаем 404

Когда fetch отклоняет Promise

Code Example 7: В каких случаях fetch отклоняет Promise? Что будет при обращении к несуществующему домену?

js
// Promise отклоняется ТОЛЬКО при сетевых ошибках:
// - Нет интернета
// - DNS не найден
// - CORS заблокирован
// - Сервер не отвечает
 
try {
  await fetch('https://несуществующий-домен.xyz');
} catch (error) {
  console.log(error.name); // TypeError
  console.log(error.message); // Failed to fetch
}

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

Code Example 8: Как отменить fetch-запрос? Что произойдёт при вызове controller.abort()?

js
const controller = new AbortController();
 
// Отменяем через 5 секунд
setTimeout(() => controller.abort(), 5000);
 
try {
  const response = await fetch('/api/slow-endpoint', {
    signal: controller.signal
  });
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Запрос отменён');
  } else {
    console.error('Ошибка:', error);
  }
}

Продвинутые аспекты

Обёртка для fetch с обработкой ошибок

Code Example 9: Зачем создавать обёртку над fetch? Какие преимущества даёт функция fetchJSON?

js
async function fetchJSON(url, options = {}) {
  const response = await fetch(url, {
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
    ...options,
  });
 
  if (!response.ok) {
    const error = new Error(`HTTP ${response.status}`);
    error.response = response;
    error.status = response.status;
    throw error;
  }
 
  return response.json();
}
 
// Использование
try {
  const users = await fetchJSON('/api/users');
} catch (error) {
  if (error.status === 404) {
    console.log('Пользователи не найдены');
  } else {
    console.error('Ошибка:', error.message);
  }
}

Параллельные запросы

Code Example 10: Как выполнить несколько fetch-запросов параллельно? Что произойдёт, если один из запросов завершится ошибкой?

js
// Запускаем все запросы одновременно
const [users, posts, comments] = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
  fetch('/api/comments').then(r => r.json())
]);
 
console.log(users, posts, comments);

Плюсы и минусы

АспектFetch APIXMLHttpRequest
Синтаксис✅ Чистый, Promise-based❌ Callback hell
Async/await✅ Нативная поддержка❌ Требует обёртку
Таймаут❌ Нужен AbortController✅ Встроенный
Прогресс❌ Нет✅ onprogress
HTTP ошибки❌ Не reject✅ Явный onerror
Поддержка✅ Современные браузеры✅ Все браузеры

Вопросы интервьюера

Q: Что возвращает fetch()?

Promise, который резолвится в объект Response. Для получения данных нужно вызвать .json(), .text() и т.д.

Q: Почему fetch не отклоняет Promise при 404 или 500?

Fetch считает успешным любой ответ от сервера. Reject происходит только при сетевых ошибках. Нужно проверять response.ok.

Q: Как отменить fetch-запрос?

Через AbortController: создать контроллер, передать signal в options, вызвать controller.abort().

Q: Чем fetch отличается от axios?

Axios: автоматический JSON, трансформаторы, интерцепторы, таймаут, отмена. Fetch: нативный, легче, но требует больше кода для типичных задач.


Источники

Code Example 1: XMLHttpRequest

❓ Что делает этот код? Какой подход к асинхронности здесь используется и какие у него недостатки?

js
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  }
};
xhr.onerror = function() {
  console.error('Ошибка');
};
xhr.send();

Code Example 2: Простой GET-запрос

❓ Оба варианта делают GET-запрос. Из скольких этапов состоит получение данных через fetch? Что происходит на каждом этапе?

Promise:

js
fetch('https://api.example.com/users')
  .then(response => {
    console.log(response.status);
    console.log(response.ok);
 
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Сетевая ошибка:', error);
  });

Async/Await:

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

Code Example 3: Методы чтения тела ответа

❓ Какие методы для чтения тела ответа показаны? Можно ли вызвать несколько из них подряд на одном response?

js
const response = await fetch('/api/data');
 
const json = await response.json();
 
const text = await response.text();
 
const blob = await response.blob();
const buffer = await response.arrayBuffer();
 
const formData = await response.formData();

Code Example 4: POST-запрос с JSON

❓ Как здесь отправляется POST-запрос? Какие обязательные настройки нужны для отправки JSON?

js
async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData),
  });
 
  if (!response.ok) {
    throw new Error(`Ошибка: ${response.status}`);
  }
 
  return response.json();
}
 
const newUser = await createUser({
  name: 'Иван',
  email: 'ivan@example.com'
});

Code Example 5: Настройки запроса (options)

❓ Какие параметры можно передать в объекте options? Для чего нужны mode, credentials и cache?

js
fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify(data),
  mode: 'cors',
  credentials: 'include',
  cache: 'no-cache',
  redirect: 'follow',
});

Code Example 6: Обработка HTTP-ошибок

❓ Чем отличается поведение этих двух вариантов при ответе сервера с кодом 404? Попадёт ли 404 в .catch() в первом варианте?

js
fetch('/api/not-found')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));
 
fetch('/api/not-found')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error(error));

Code Example 7: Когда fetch отклоняет Promise

❓ В каких случаях fetch отклоняет Promise? Что будет при обращении к несуществующему домену?

js
try {
  await fetch('https://несуществующий-домен.xyz');
} catch (error) {
  console.log(error.name);
  console.log(error.message);
}

Code Example 8: Отмена запроса с AbortController

❓ Как отменить fetch-запрос? Что произойдёт при вызове controller.abort()?

js
const controller = new AbortController();
 
setTimeout(() => controller.abort(), 5000);
 
try {
  const response = await fetch('/api/slow-endpoint', {
    signal: controller.signal
  });
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Запрос отменён');
  } else {
    console.error('Ошибка:', error);
  }
}

Code Example 9: Обёртка для fetch с обработкой ошибок

❓ Зачем создавать обёртку над fetch? Какие преимущества даёт функция fetchJSON?

js
async function fetchJSON(url, options = {}) {
  const response = await fetch(url, {
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
    ...options,
  });
 
  if (!response.ok) {
    const error = new Error(`HTTP ${response.status}`);
    error.response = response;
    error.status = response.status;
    throw error;
  }
 
  return response.json();
}
 
try {
  const users = await fetchJSON('/api/users');
} catch (error) {
  if (error.status === 404) {
    console.log('Пользователи не найдены');
  } else {
    console.error('Ошибка:', error.message);
  }
}

Code Example 10: Параллельные запросы

❓ Как выполнить несколько fetch-запросов параллельно? Что произойдёт, если один из запросов завершится ошибкой?

js
const [users, posts, comments] = await Promise.all([
  fetch('/api/users').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
  fetch('/api/comments').then(r => r.json())
]);
 
console.log(users, posts, comments);