JavaScript · API · Просунутий

Обробка помилок

5 завдань

Надійно обробляйте HTTP-помилки та збої мережі за допомогою response.ok і try/catch.

HTTP-помилки та збої мережі

#
На відміну від `axios`, звичайний `fetch` **ніколи не кидає виняток при HTTP-помилках** (4xx, 5xx). Відповідь 404 або 500 повертається нормально — ви самі маєте перевірити `response.ok`: ```javascript const response = await fetch(url); if (!response.ok) { // 4xx або 5xx — обробіть тут console.log('Помилка:', response.status); } else { const data = await response.json(); } ``` `response.ok` дорівнює `true` для 200–299, `false` для всього іншого. `fetch` кидає виняток (відхиляє Promise) лише при **збоях мережі** — немає інтернету, помилка DNS, запит перервано: ```javascript try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); } catch (err) { if (err instanceof TypeError) { console.log('Помилка мережі:', err.message); } else { console.log('HTTP-помилка:', err.message); } } ``` AbortController дозволяє встановити таймаут: ```javascript const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); // таймаут 5 секунд try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeout); return response.json(); } catch (err) { if (err.name === 'AbortError') console.log('Запит перевищив час очікування'); else throw err; } ```

Практичні паттерни обробки помилок

#
Обгортка `safeFetch`, що обробляє всі випадки: ```javascript async function safeFetch(url, options = {}) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeout); if (!response.ok) { const body = await response.text(); throw new Error(`HTTP ${response.status}: ${body}`); } return await response.json(); } catch (err) { if (err.name === 'AbortError') { console.error('Запит перевищив час очікування:', url); } else if (err instanceof TypeError) { console.error('Помилка мережі:', err.message); } else { console.error('Помилка:', err.message); } return null; } } // Використання const data = await safeFetch('https://apilearn.tukas.dev/api/products/'); if (data) console.log(data.count); ``` Окрема обробка 401: ```javascript async function fetchWithAuth(url, headers) { const response = await fetch(url, { headers }); if (response.status === 401) { console.log('Не авторизовано — перевірте токен'); return null; } if (!response.ok) { console.log(`HTTP-помилка: ${response.status}`); return null; } return response.json(); } ``` Повтор при 429 (ліміт запитів) або 503 (сервіс недоступний): ```javascript async function fetchWithRetry(url, options = {}, retries = 3) { for (let attempt = 0; attempt < retries; attempt++) { const response = await fetch(url, options); if ([429, 503].includes(response.status) && attempt < retries - 1) { const wait = 2 ** attempt * 1000; // 1с, 2с, 4с console.log(`Спроба ${attempt + 1} невдала, повтор через ${wait}мс...`); await new Promise(r => setTimeout(r, wait)); continue; } return response; } } ```

Довідник обробки помилок

#
**Перевірка response.ok (завжди робіть це)** ```javascript const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); ``` **fetch кидає виняток лише при мережевих помилках** ``` TypeError Збій мережі (офлайн, помилка DNS, блокування CORS) AbortError AbortController.abort() був викликаний (наприклад, таймаут) // 4xx/5xx — НЕ кидається, перевіряйте response.ok вручну ``` **Таймаут через AbortController** ```javascript const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); const response = await fetch(url, { signal: controller.signal }); ``` **Паттерн catch** ```javascript try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (err) { if (err.name === 'AbortError') { /* таймаут */ } else if (err instanceof TypeError) { /* мережа */ } else { /* HTTP-помилка від нашого throw */ } } ``` **Коди статусів** ``` 200 OK 201 Created 204 No Content (успішне DELETE — без тіла) 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 429 Too Many Requests 500 Internal Server Error 503 Service Unavailable ```
01

Перевірити response.ok

#

Виконайте запит до `/api/products/99999/` (неіснуючий). Виведіть `response.status` і `response.ok`. Потім виконайте запит до `/api/products/1/` і порівняйте.

const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  // запит до неіснуючого товару — вивести status і ok
  // запит до реального товару — вивести status і ok
}

main();
Рішення
const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  const r1 = await fetch(`${BASE_URL}/api/products/99999/`);
  console.log(r1.status, r1.ok);   // 404 false

  const r2 = await fetch(`${BASE_URL}/api/products/1/`);
  console.log(r2.status, r2.ok);   // 200 true
}

main();
02

Кинути виняток при HTTP-помилці

#

Напишіть функцію `getProduct(id)`, яка отримує товар і кидає `Error`, якщо `response.ok` дорівнює `false`. Викличте її з дійсним id та з `99999`. Перехопіть і виведіть повідомлення про помилку.

const BASE_URL = 'https://apilearn.tukas.dev';

async function getProduct(id) {
  // fetch, перевірити response.ok, кинути виняток якщо не ok
}

async function main() {
  // викликати з дійсним id
  // викликати з 99999 і перехопити помилку
}

main();
Рішення
const BASE_URL = 'https://apilearn.tukas.dev';

async function getProduct(id) {
  const response = await fetch(`${BASE_URL}/api/products/${id}/`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

async function main() {
  try {
    const p = await getProduct(1);
    console.log(p.name);
  } catch (err) {
    console.log('Error:', err.message);
  }

  try {
    await getProduct(99999);
  } catch (err) {
    console.log('Error:', err.message);  // HTTP 404
  }
}

main();
03

Обробити 401 Unauthorized

#

Спробуйте запитати `/api/users/profile/` без токена. Перевірте `response.status` — якщо це 401, виведіть конкретне повідомлення. Інакше виведіть дані.

const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  // запит профілю без авторизації, окрема обробка 401
}

main();
Рішення
const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  const response = await fetch(`${BASE_URL}/api/users/profile/`);
  if (response.status === 401) {
    console.log('Unauthorized — provide a valid token');
  } else if (!response.ok) {
    console.log(`HTTP error: ${response.status}`);
  } else {
    const data = await response.json();
    console.log(data.username);
  }
}

main();
04

Обгортка safeFetch

#

Напишіть функцію `safeFetch(url)`, яка перевіряє `response.ok` і повертає `null` (виводячи помилку) при збої, або розібраний JSON при успіху. Перевірте з дійсним URL та URL що повертає 404.

const BASE_URL = 'https://apilearn.tukas.dev';

async function safeFetch(url) {
  // fetch, перевірити ok, повернути json або null
}

async function main() {
  const good = await safeFetch(`${BASE_URL}/api/products/1/`);
  console.log(good ? good.name : 'null');

  const bad = await safeFetch(`${BASE_URL}/api/products/99999/`);
  console.log(bad);
}

main();
Рішення
const BASE_URL = 'https://apilearn.tukas.dev';

async function safeFetch(url) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      console.error(`HTTP ${response.status} for ${url}`);
      return null;
    }
    return response.json();
  } catch (err) {
    console.error('Network error:', err.message);
    return null;
  }
}

async function main() {
  const good = await safeFetch(`${BASE_URL}/api/products/1/`);
  console.log(good ? good.name : 'null');

  const bad = await safeFetch(`${BASE_URL}/api/products/99999/`);
  console.log(bad);
}

main();
05

Таймаут через AbortController

#

Напишіть функцію `fetchWithTimeout(url, ms)`, яка перериває запит, якщо він виконується довше ніж `ms` мілісекунд. Перевірте на звичайному запиті та виведіть результат або `"timed out"`.

const BASE_URL = 'https://apilearn.tukas.dev';

async function fetchWithTimeout(url, ms = 3000) {
  // використати AbortController для переривання через ms мілісекунд
}

async function main() {
  const data = await fetchWithTimeout(`${BASE_URL}/api/products/`, 5000);
  console.log(data ? data.count : 'timed out');
}

main();
Рішення
const BASE_URL = 'https://apilearn.tukas.dev';

async function fetchWithTimeout(url, ms = 3000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ms);
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timer);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('Request timed out');
      return null;
    }
    throw err;
  }
}

async function main() {
  const data = await fetchWithTimeout(`${BASE_URL}/api/products/`, 5000);
  console.log(data ? data.count : 'timed out');
}

main();