Learn JavaScript

fetch GET: запрос и разбор JSON

fetch - стандартный способ делать HTTP-запросы в браузере. Самый частый сценарий - GET-запрос и разбор JSON.

Важно помнить:

  • fetch возвращает Promise
  • response.json() тоже возвращает Promise
  • fetch не считает HTTP 404/500 ошибкой автоматически - response.ok нужно проверять вручную

1) Самый простой GET + JSON

fetch("/api/data")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  });

Этот вариант работает, но здесь нет проверки статуса и обработки ошибок.

2) Правильный базовый шаблон: проверка response.ok

fetch("/api/data")
  .then((response) => {
    if (!response.ok) {
      throw new Error("HTTP " + response.status);
    }
    return response.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e.message);
  });

3) Тот же шаблон через async/await

async function loadJSON(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  return response.json();
}

async function run() {
  try {
    const data = await loadJSON("/api/data");
    console.log(data);
  } catch (e) {
    console.log(e.message);
  }
}

run();

4) Что такое Response: важные свойства

После fetch получается объект Response.

Полезные свойства:

  • response.ok - true, если статус 200-299
  • response.status - код ответа (200, 404, 500 и т.д.)
  • response.headers - заголовки
  • response.url - итоговый URL

Пример:

async function run() {
  const response = await fetch("/api/data");

  console.log(response.ok);
  console.log(response.status);
  console.log(response.url);
}

run();

5) Разбор JSON: что может пойти не так

response.json() выбросит ошибку, если сервер вернул не JSON или вернул битый JSON.

Поэтому правильно:

  • сначала проверить response.ok
  • затем парсить JSON
  • и все это завернуть в обработку ошибок
async function loadJSON(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  const data = await response.json();
  return data;
}

6) Query-параметры в GET: URL и URLSearchParams

Если нужно сформировать URL с параметрами, удобно использовать URL и URLSearchParams.

const url = new URL("/api/search", location.origin);
url.searchParams.set("q", "js");
url.searchParams.set("page", "1");

fetch(url)
  .then((r) => r.json())
  .then((data) => {
    console.log(data);
  });

7) Таймаут: fetch сам по себе не прерывается

По умолчанию fetch может ждать долго. Для таймаута используют AbortController.

async function fetchWithTimeout(url, ms) {
  const controller = new AbortController();
  const timerId = setTimeout(() => controller.abort(), ms);

  try {
    const response = await fetch(url, { signal: controller.signal });
    return response;
  } finally {
    clearTimeout(timerId);
  }
}

async function run() {
  try {
    const response = await fetchWithTimeout("/api/data", 2000);

    if (!response.ok) {
      throw new Error("HTTP " + response.status);
    }

    const data = await response.json();
    console.log(data);
  } catch (e) {
    console.log(e.name);
    console.log(e.message);
  }
}

run();

Если запрос прерван, ошибка обычно будет с name === “AbortError”.

8) Частые ошибки

1) Забыть await у response.json()

async function run() {
  const response = await fetch("/api/data");
  const data = response.json();
  console.log(data);
}

data здесь Promise. Нужно:

async function run() {
  const response = await fetch("/api/data");
  const data = await response.json();
  console.log(data);
}

2) Считать 404 “ошибкой fetch”

fetch("/api/missing")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  });

Если сервер вернет 404 и не JSON, будет ошибка на json(). Правильнее проверять ok.

Что стоит запомнить

  • fetch возвращает Promise с объектом Response.
  • HTTP 404/500 не бросают ошибку автоматически - нужно проверять response.ok.
  • response.json() тоже асинхронный и возвращает Promise.
  • Для query-параметров удобно URLSearchParams.
  • Для таймаута подходит AbortController.