Введение

В этой лабораторной работе вы освоите основные навыки взаимодействия с веб-API с использованием современного JavaScript. Вы изучите fetch API — мощный и гибкий инструмент, встроенный в веб-браузеры, для выполнения асинхронных сетевых запросов. Основная цель — понять, как запрашивать данные с сервера, обрабатывать ответ и управлять потенциальными ошибками, что является основополагающим навыком для создания динамических веб-приложений.

Вы начнете с выполнения простого GET-запроса для получения данных, а затем научитесь обрабатывать ответ, парсить его как JSON и отображать полученные данные в HTML-элементе. Лабораторная работа также охватывает такие важные аспекты, как реализация надежной обработки ошибок, выполнение POST-запросов для отправки данных на сервер и аутентификация ваших запросов с использованием API-ключа.

Выполнение базового GET-запроса с помощью fetch API

На этом шаге вы научитесь выполнять фундаментальный API-вызов в JavaScript: GET-запрос. Мы будем использовать fetch API — современный, мощный и гибкий инструмент, встроенный во все современные веб-браузеры и доступный в средах Node.js 18+. Он позволяет нам асинхронно запрашивать ресурсы с сервера.

Функция fetch основана на промисах (promises), что означает, что она возвращает Promise, который разрешается в Response (ответ) на этот запрос, независимо от того, был ли он успешным или нет. Это позволяет нам обрабатывать результат асинхронной операции по ее завершении.

Сначала создадим файл для написания нашего кода. В файловом проводнике слева от вашего экрана создайте новый файл с именем index.js в директории ~/project.

Для этого примера мы будем использовать API JSONPlaceholder. Это бесплатный, фейковый онлайн REST API, который идеально подходит для тестирования и прототипирования. Мы запросим один элемент "todo".

Теперь добавьте следующий код в ваш файл index.js. Этот код определяет URL API-эндпоинта и использует fetch для выполнения GET-запроса.

// Определяем URL API для одного элемента todo
const apiUrl = "https://jsonplaceholder.typicode.com/todos/1";

// Выполняем GET-запрос с помощью fetch API
fetch(apiUrl)
  .then((response) => {
    // Функция fetch возвращает промис.
    // Первый блок .then() получает объект Response.
    // Нам нужно вызвать метод .json() у ответа, чтобы разобрать тело ответа как JSON.
    return response.json();
  })
  .then((data) => {
    // Второй блок .then() получает разобранные JSON-данные.
    console.log("Данные успешно получены:");
    console.log(data);
  })
  .catch((error) => {
    // Блок .catch() будет выполнен, если во время операции fetch произойдет какая-либо ошибка.
    console.error("Ошибка при получении данных:", error);
  });

Разберем код:

  • apiUrl: Мы сохраняем URL API-эндпоинта, к которому хотим обратиться, в константе.
  • fetch(apiUrl): Это инициирует GET-запрос по указанному URL и возвращает Promise.
  • .then(response => response.json()): Когда Promise разрешается, вызывается эта функция. Объект response — это не фактические JSON-данные, а представление всего HTTP-ответа. Мы вызываем метод response.json(), чтобы извлечь содержимое тела JSON, которое само по себе возвращает другой Promise.
  • .then(data => { ... }): Этот второй .then() обрабатывает Promise, возвращаемый response.json(). Параметр data теперь содержит фактический JSON-объект из API. Мы выводим эти данные в консоль.
  • .catch(error => { ... }): Если Promise отклоняется в любой момент (например, из-за сетевой ошибки), этот блок перехватит ошибку и выведет ее в консоль.

Чтобы выполнить ваш скрипт и увидеть результат, откройте новый терминал в WebIDE и выполните следующую команду:

node ~/project/index.js

Вы должны увидеть полученные данные, выведенные в вашу консоль, которые представляют собой один элемент "todo" из API.

Ожидаемый вывод:

Data fetched successfully:
{ userId: 1, id: 1, title: 'delectus aut autem', completed: false }
Пример GET-запроса JavaScript fetch API

Обработка ответа и парсинг JSON-данных

На этом шаге мы глубже изучим обработку ответа от API-вызова. Когда вы используете fetch, сервер отправляет обратно объект Response. Мы рассмотрим, что содержит этот объект и как правильно парсить JSON-данные из его тела для использования в вашем приложении.

Объект Response, который вы получаете в первом блоке .then(), сам по себе не является данными. Это представление всего HTTP-ответа, включая коды состояния (например, 200 для OK), заголовки и тело ответа. Тело представляет собой поток данных, и для его использования необходимо его прочитать.

fetch API предоставляет несколько методов для этого, таких как .text() для обычного текста и .json() для парсинга JSON-данных. Метод .json() считывает поток ответа до конца и возвращает новый промис, который разрешается результатом парсинга текста тела как JavaScript-объекта. Именно поэтому мы связываем второй .then() для работы с фактическими данными.

Давайте модифицируем наш файл ~/project/index.js, чтобы продемонстрировать это. Вместо того чтобы просто выводить весь объект данных, мы получим доступ к его конкретным свойствам и выведем их, показывая, что у нас есть обычный JavaScript-объект для работы.

Обновите содержимое вашего файла ~/project/index.js следующим кодом:

// Определяем URL API для одного элемента todo
const apiUrl = "https://jsonplaceholder.typicode.com/todos/1";

fetch(apiUrl)
  .then((response) => {
    // Метод response.json() парсит JSON-тело ответа
    // и возвращает промис, который разрешается с результирующим JavaScript-объектом.
    return response.json();
  })
  .then((data) => {
    // Теперь 'data' — это JavaScript-объект. Мы можем получить доступ к его свойствам.
    console.log("JSON-данные успешно разобраны:");
    console.log(`Название задачи: ${data.title}`);
    console.log(`Выполнено: ${data.completed}`);
  })
  .catch((error) => {
    // Блок .catch() будет выполнен, если во время операции fetch произойдет какая-либо ошибка.
    console.error("Ошибка при получении или парсинге данных:", error);
  });

В этом обновленном коде второй блок .then() теперь получает data как JavaScript-объект. Мы используем шаблонные литералы (синтаксис с обратными кавычками `) для создания строк, включающих значения data.title и data.completed, демонстрируя, что JSON был успешно разобран.

Теперь снова выполните скрипт в вашем терминале, чтобы увидеть новый вывод:

node ~/project/index.js

Вы увидите более структурированный вывод, подтверждающий, что вы успешно получили доступ к свойствам разобранного JSON-объекта.

Ожидаемый вывод:

Successfully parsed JSON data:
Todo Title: delectus aut autem
Is Completed: false

Отображение полученных данных в HTML-элементе

На этом шаге вы научитесь брать данные, полученные из API, и отображать их на веб-странице. Хотя вывод данных в консоль полезен для разработки, конечная цель часто заключается в представлении этой информации пользователю. Для этого требуется HTML-файл для структурирования контента и JavaScript для манипулирования объектной моделью документа (DOM).

Сначала нам нужен HTML-файл. В файловом проводнике слева создайте новый файл с именем index.html в директории ~/project.

Добавьте следующую базовую HTML-структуру в ваш файл index.html. Этот файл включает заголовок и элемент div с идентификатором data-output, который будет служить контейнером для наших полученных данных. Тег <script> в конце тела гарантирует, что наш JavaScript будет выполнен после загрузки HTML-элементов.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>API Data Display</title>
  </head>
  <body>
    <h1>Fetched Todo Item</h1>
    <div id="data-output">
      <p>Loading data...</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Далее вам нужно будет изменить ваш файл ~/project/index.js, чтобы он взаимодействовал с этим HTML. Вместо вывода в консоль скрипт найдет элемент div по его идентификатору и обновит его содержимое данными из API.

Замените содержимое ~/project/index.js следующим кодом:

const apiUrl = "https://jsonplaceholder.typicode.com/todos/1";

// Выбираем HTML-элемент, где мы будем отображать данные
const outputElement = document.getElementById("data-output");

fetch(apiUrl)
  .then((response) => response.json())
  .then((data) => {
    // Как только мы получим данные, мы обновим содержимое HTML.
    // Мы используем innerHTML, чтобы заменить сообщение "Loading..." структурированными данными.
    outputElement.innerHTML = `
      <p><strong>Title:</strong> ${data.title}</p>
      <p><strong>Completed:</strong> ${data.completed}</p>
    `;
  })
  .catch((error) => {
    // Если произойдет ошибка, отобразим сообщение об ошибке пользователю.
    outputElement.textContent = "Failed to load data.";
    console.error("Error fetching data:", error);
  });

Теперь, чтобы увидеть результат, вам нужно будет предоставить эти файлы через веб-сервер. Среда LabEx включает Python, который имеет простой встроенный веб-сервер.

  1. Откройте новый терминал в WebIDE.
  2. Запустите веб-сервер, выполнив следующую команду. Это позволит обслуживать файлы из текущей директории (~/project) на порту 8080.
python3 -m http.server 8080
  1. Платформа LabEx обнаружит эту запущенную службу и предоставит вкладку "Web 8080" для предварительного просмотра вашей страницы index.html.
Веб-сервер, запущенный в терминале, и вкладка предварительного просмотра

Теперь вы должны увидеть на своей веб-странице заголовок и статус выполнения полученного элемента "todo", которые заменят исходное сообщение "Loading data...".

Реализация обработки ошибок для API-запросов

На этом шаге мы сделаем наш API-вызов более надежным, реализовав надлежащую обработку ошибок. Запросы к API могут завершаться неудачно по различным причинам, таким как неправильный URL, проблемы с сетью или проблемы на стороне сервера. Крайне важно корректно обрабатывать эти потенциальные сбои, чтобы обеспечить лучший пользовательский опыт.

Важной деталью fetch API является то, что его промис не отклоняется при ошибках HTTP-статуса, таких как 404 (Not Found) или 500 (Internal Server Error). Он отклоняется только в случае сетевого сбоя, который препятствует завершению запроса. Для обработки HTTP-ошибок нам нужно проверить свойство response.ok, которое имеет значение true для успешных ответов (коды состояния 200-299).

Давайте обновим наш ~/project/index.js, чтобы проверять статус ответа и обрабатывать потенциальные ошибки.

Замените содержимое вашего файла ~/project/index.js следующим кодом. Мы добавляем проверку внутри первого блока .then().

const apiUrl = "https://jsonplaceholder.typicode.com/todos/1";
const outputElement = document.getElementById("data-output");

fetch(apiUrl)
  .then((response) => {
    // Проверяем, был ли ответ успешным.
    // Свойство 'ok' — это булево значение, которое равно true, если код состояния находится в диапазоне 200-299.
    if (!response.ok) {
      // Если нет, мы выбрасываем ошибку, которая будет перехвачена блоком .catch().
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then((data) => {
    outputElement.innerHTML = `
      <p><strong>Title:</strong> ${data.title}</p>
      <p><strong>Completed:</strong> ${data.completed}</p>
    `;
  })
  .catch((error) => {
    // Отображаем понятное пользователю сообщение об ошибке в HTML-элементе.
    outputElement.textContent = "Failed to load data. Please try again later.";
    // Записываем техническую ошибку в консоль для целей отладки.
    console.error("Error fetching data:", error);
  });

Чтобы увидеть нашу обработку ошибок в действии, намеренно используем неверный URL API. Измените apiUrl в ~/project/index.js, чтобы он указывал на несуществующий ресурс. Это приведет к тому, что API вернет ошибку 404 Not Found.

Измените эту строку в вашем файле index.js:
const apiUrl = 'https://jsonplaceholder.typicode.com/todos/1';

На эту:
const apiUrl = 'https://jsonplaceholder.typicode.com/invalid-path/1';

Теперь посмотрим на результат. Если ваш веб-сервер Python из предыдущего шага все еще запущен, просто обновите вкладку предварительного просмотра в браузере. Если вы его остановили, запустите его снова из терминала:

python3 -m http.server 8080

Затем откройте предварительный просмотр. Вместо данных вы должны увидеть сообщение об ошибке, отображаемое на странице, поскольку наша проверка if (!response.ok) перехватила ошибку 404.

Ожидаемый вывод на веб-странице:

Failed to load data. Please try again later.

Важно: Прежде чем переходить к следующему шагу, не забудьте изменить apiUrl обратно на правильный: https://jsonplaceholder.typicode.com/todos/1.

Отправка POST-запроса для передачи данных

На этом шаге вы научитесь отправлять данные на сервер с помощью POST-запроса. До сих пор мы только получали (или "GET") данные. POST-запрос используется для отправки данных на указанный ресурс, часто вызывая изменение состояния или создание новой записи на сервере.

Для этого нам нужно предоставить функции fetch больше информации, включая метод запроса, заголовки для описания отправляемых данных и сами данные в теле запроса.

Сначала обновим наш файл ~/project/index.html, чтобы включить форму. Это позволит нам вводить данные, которые затем можно будет отправить в API. Замените все содержимое index.html следующим кодом:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>POST Request Example</title>
  </head>
  <body>
    <h1>Create a New Todo Item</h1>
    <form id="add-todo-form">
      <input
        type="text"
        id="todo-title"
        placeholder="Enter a new todo title"
        required
      />
      <button type="submit">Add Todo</button>
    </form>
    <hr />
    <h2>Server Response:</h2>
    <div id="response-output"></div>

    <script src="index.js"></script>
  </body>
</html>

Этот HTML создает простую форму с текстовым полем ввода и кнопкой отправки, а также div для отображения ответа сервера.

Далее мы полностью заменим код в ~/project/index.js, чтобы обрабатывать отправку формы и выполнять POST-запрос.

Замените содержимое файла ~/project/index.js следующим кодом:

// Конечная точка API для создания новых todo
const apiUrl = "https://jsonplaceholder.typicode.com/todos";

// Получаем форму и элемент вывода ответа из DOM
const todoForm = document.getElementById("add-todo-form");
const responseOutput = document.getElementById("response-output");

// Добавляем обработчик событий для события отправки формы
todoForm.addEventListener("submit", function (event) {
  // Предотвращаем стандартное поведение отправки формы
  event.preventDefault();

  // Получаем заголовок из поля ввода
  const todoTitle = document.getElementById("todo-title").value;

  // Данные, которые мы хотим отправить в POST-запросе
  const newTodo = {
    title: todoTitle,
    completed: false,
    userId: 1
  };

  // Опции для fetch-запроса
  const requestOptions = {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newTodo)
  };

  // Выполняем POST-запрос
  fetch(apiUrl, requestOptions)
    .then((response) => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      // API возвращает созданный объект, включая новый 'id'
      return response.json();
    })
    .then((data) => {
      // Отображаем ответ сервера в нашем div для вывода
      responseOutput.innerHTML = `<p>Successfully created todo!</p><pre>${JSON.stringify(data, null, 2)}</pre>`;
    })
    .catch((error) => {
      responseOutput.textContent = "Failed to create todo.";
      console.error("Error:", error);
    });
});

Давайте разберем новый объект requestOptions:

  • method: 'POST': Это указывает fetch выполнить POST-запрос.
  • headers: { 'Content-Type': 'application/json' }: Этот заголовок информирует сервер о том, что данные в теле находятся в формате JSON.
  • body: JSON.stringify(newTodo): Это фактические данные, которые мы отправляем. Их необходимо преобразовать в строку JSON перед отправкой.

Теперь снова запустите ваш веб-сервер, если он не запущен:

python3 -m http.server 8080

Откройте предварительный просмотр, введите заголовок для нового элемента todo в поле ввода и нажмите кнопку "Add Todo". Вы должны увидеть сообщение об успехе и данные, возвращенные сервером, которые включают новый id для созданного вами элемента.

Пример успешного ответа на POST-запрос

Аутентификация запросов с помощью API-ключа

На этом шаге вы научитесь аутентифицировать ваши API-запросы с помощью API-ключа. Многие API требуют аутентификации для идентификации пользователя, контроля доступа к данным и отслеживания использования. API-ключ — это уникальная строка символов, которую вы включаете в свой запрос, чтобы подтвердить, что у вас есть разрешение на использование API.

Существует несколько способов отправки API-ключа, но распространенным и безопасным методом является включение его в заголовки запроса, обычно с использованием заголовка Authorization.

В этом примере мы симулируем получение защищенных данных пользователя, которые требуют API-ключ. Мы изменим наш код, чтобы включить заголовок Authorization с токеном "Bearer", что является стандартным способом отправки учетных данных аутентификации.

Сначала упростим наш файл ~/project/index.html, чтобы он просто отображал полученные данные пользователя. Замените его содержимое следующим кодом:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Authenticated API Request</title>
  </head>
  <body>
    <h1>User Profile (Protected)</h1>
    <div id="user-profile">
      <p>Loading user data...</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Затем замените содержимое файла ~/project/index.js приведенным ниже кодом. Этот скрипт выполнит GET-запрос для получения данных пользователя и включит поддельный API-ключ в заголовки.

// Определяем URL API для профиля пользователя
const apiUrl = "https://jsonplaceholder.typicode.com/users/1";

// В реальном приложении вы получите этот ключ от вашего поставщика API.
const apiKey = "YOUR_SECRET_API_KEY_HERE";

// Выбираем HTML-элемент для вывода
const userProfileElement = document.getElementById("user-profile");

// Создаем объект requestOptions, включая заголовки для аутентификации
const requestOptions = {
  method: "GET",
  headers: {
    Authorization: `Bearer ${apiKey}`
  }
};

// Выполняем аутентифицированный GET-запрос
fetch(apiUrl, requestOptions)
  .then((response) => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then((data) => {
    // Отображаем полученные данные пользователя
    userProfileElement.innerHTML = `
      <p><strong>Name:</strong> ${data.name}</p>
      <p><strong>Email:</strong> ${data.email}</p>
      <p><strong>Website:</strong> ${data.website}</p>
    `;
  })
  .catch((error) => {
    userProfileElement.textContent = "Failed to load user profile.";
    console.error("Error:", error);
  });

В этом коде:

  • Мы определяем заполнитель apiKey.
  • Мы создаем объект requestOptions.
  • Внутри headers мы добавляем ключ Authorization. Значение Bearer ${apiKey} является распространенным форматом, где "Bearer" — это тип токена, за которым следует сам ключ.

Примечание: Используемый нами JSONPlaceholder API является общедоступным и на самом деле не требует API-ключа. Он просто проигнорирует этот заголовок. Однако этот код демонстрирует стандартный метод, который вы будете использовать для многих реальных API, требующих аутентификации.

Чтобы увидеть результат, запустите ваш веб-сервер, если он еще не запущен:

python3 -m http.server 8080

Затем откройте предварительный просмотр. Страница успешно загрузится и отобразит профиль пользователя, демонстрируя, что вы правильно структурировали аутентифицированный API-запрос.

Резюме

В этой лабораторной работе вы научились вызывать API в JavaScript, используя современный API fetch. Вы начали с выполнения базового GET-запроса для получения данных из общедоступной конечной точки API. Вы практиковались в обработке асинхронной природы fetch с использованием промисов, объединяя блоки .then() для обработки ответа и парсинга текста тела в формате JSON. Ключевые навыки включали динамическое отображение полученных данных в HTML-элементе и реализацию надежной обработки ошибок с помощью блока .catch() для управления потенциальными сбоями сети.

Опираясь на эти основы, вы изучили, как отправлять данные на сервер, конструируя и выполняя POST-запрос, включая необходимые заголовки и JSON-полезную нагрузку. Наконец, вы узнали распространенный метод обеспечения безопасности API-коммуникаций путем аутентификации ваших запросов, который включал включение API-ключа для получения авторизованного доступа к защищенным ресурсам.