JavaScript · API · Advanced

Error Handling

5 tasks

Handle HTTP errors and network failures robustly using response.ok and try/catch.

HTTP errors and network failures

#
Unlike `axios`, plain `fetch` **never throws on HTTP error codes** (4xx, 5xx). A 404 or 500 response returns normally — you have to check `response.ok` yourself: ```javascript const response = await fetch(url); if (!response.ok) { // 4xx or 5xx — handle it here console.log('Error:', response.status); } else { const data = await response.json(); } ``` `response.ok` is `true` for 200–299, `false` for everything else. `fetch` only throws (rejects the Promise) for **network failures** — no internet, DNS failure, request aborted: ```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('Network error:', err.message); } else { console.log('HTTP error:', err.message); } } ``` AbortController lets you set a timeout: ```javascript const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout try { const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeout); return response.json(); } catch (err) { if (err.name === 'AbortError') console.log('Request timed out'); else throw err; } ```

Practical error handling patterns

#
A `safeFetch` wrapper that handles all cases: ```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('Request timed out:', url); } else if (err instanceof TypeError) { console.error('Network error:', err.message); } else { console.error('Error:', err.message); } return null; } } // Usage const data = await safeFetch('https://apilearn.tukas.dev/api/products/'); if (data) console.log(data.count); ``` Handle 401 specifically: ```javascript async function fetchWithAuth(url, headers) { const response = await fetch(url, { headers }); if (response.status === 401) { console.log('Unauthorized — check your token'); return null; } if (!response.ok) { console.log(`HTTP error: ${response.status}`); return null; } return response.json(); } ``` Retry on 429 (rate limit) or 503 (service unavailable): ```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; // 1s, 2s, 4s console.log(`Attempt ${attempt + 1} failed, retrying in ${wait}ms...`); await new Promise(r => setTimeout(r, wait)); continue; } return response; } } ```

Error handling reference

#
**response.ok check (always do this)** ```javascript const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); ``` **fetch only throws for network errors** ``` TypeError Network failure (offline, DNS failure, CORS block) AbortError AbortController.abort() was called (e.g. timeout) // 4xx/5xx — NOT thrown, check response.ok manually ``` **Timeout with AbortController** ```javascript const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); const response = await fetch(url, { signal: controller.signal }); ``` **Catch pattern** ```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') { /* timeout */ } else if (err instanceof TypeError) { /* network */ } else { /* HTTP error from our throw */ } } ``` **Status code constants** ``` 200 OK 201 Created 204 No Content (DELETE success — no body) 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 429 Too Many Requests 500 Internal Server Error 503 Service Unavailable ```
01

Check response.ok

#

Fetch `/api/products/99999/` (non-existent). Print `response.status` and `response.ok`. Then fetch `/api/products/1/` and compare.

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

async function main() {
  // fetch missing product — print status and ok
  // fetch real product — print status and ok
}

main();
Solution
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

Throw on HTTP error

#

Write a `getProduct(id)` function that fetches a product and throws an `Error` if `response.ok` is false. Call it with a valid id and with `99999`. Catch and print the error message.

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

async function getProduct(id) {
  // fetch, check response.ok, throw if not ok
}

async function main() {
  // call with valid id
  // call with 99999 and catch the error
}

main();
Solution
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

Handle 401 Unauthorized

#

Try to fetch `/api/users/profile/` without a token. Check `response.status` — if it's 401, print a specific message. Otherwise print the data.

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

async function main() {
  // fetch profile without auth, handle 401 specifically
}

main();
Solution
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 wrapper

#

Write a `safeFetch(url)` function that checks `response.ok` and returns `null` (printing the error) on failure, or the parsed JSON on success. Test with a valid URL and a 404 URL.

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

async function safeFetch(url) {
  // fetch, check ok, return json or 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();
Solution
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

Timeout with AbortController

#

Write a `fetchWithTimeout(url, ms)` function that aborts the request if it takes longer than `ms` milliseconds. Test it with a normal request and print the result or `"timed out"`.

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

async function fetchWithTimeout(url, ms = 3000) {
  // use AbortController to abort after ms milliseconds
}

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

main();
Solution
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();