JavaScript Fetch API

Содержание:
  1. Что такое Fetch API?
  2. Отправка запроса и чтение ответа
  3. Пример использования fetch() для получения JSON
  4. Пример простого запроса HTTP POST
  5. Комментарии

В этой статье познакомимся с API-интерфейсом JavaScript Fetch и узнаем о том, как его использовать для выполнения асинхронных HTTP-запросов.

Что такое Fetch API?

Fetch API – это простой и современный интерфейс, который позволяет выполнять сетевые запросы и обрабатывать ответы от сервера. Т.е. решает те же задачи, что XMLHttpRequest (XHR)

Самым большим отличием Fetch от XMLHttpRequest является то, что первый использует промисы, которые значительно упрощают работу с запросами и ответами. Код на Fetch получается более простым и чистым.

Начиная с ES7, вы можете использовать async-await и полностью избавиться от обещаний.

Fetch API предоставляет глобальный метод fetch():

JavaScript
// url – Переменная, содержащая URL для отправки запроса
// options – различные настройки: метод, заголовки, режим и т.д.
let request = fetch(url, [options])

Отправка запроса и чтение ответа

Если не указывать метод, то Fetch API по умолчанию делает GET-запрос.

JavaScript
// url – переменная, содержащая URL
let request = fetch(url);

Метод fetch() возвращает promise. Для обработки результата можно использовать методы then() и catch():

JavaScript
let request = fetch('/examples/ajax/01.html');
request
  .then(response => {
    // обработка ответа
    console.log( response );
  })
  .catch(error => {
    // обработка ошибки
    console.log( error );
  });

При успешном выполнении запроса, мы получим объект Response. У Response есть ряд полезных свойств для проверки состояния ответа:

  • status – код статуса;
  • statusText – текст статуса;
  • oktrue, когда код статуса от 200 до 299;
  • redirectedtrue, если при вызове запрошенного URL-адреса произошёл редирект.

Проверить, выполнен ли запрос успешно, можно с помощью свойства ok:

JavaScript
let request = fetch('/examples/ajax/01.html');
request
  .then(response => {
    // если код статуса ответа от 200 до 299
    if (response.ok) {
      // запрос был успешно выполнен сервером
    }
  })
  .catch(error => {
    // обработка ошибки
    console.log( error );
  });

Промис (request) завершается успешно даже когда запрошенный URL не существует (код ответа 404) или он вызывает ошибку 500. Просто будут другие значения свойств: status, ok и т.д.

В метод catch мы попадём только в том случае, когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения.

Для получения тела ответа, у объекта Response имеются следующие методы:

  • text() – как текст;
  • json() – в формате JSON;
  • formData() – как объект FormData;
  • blob() – в формате Blob;
  • arrayBuffer() – как ArrayBuffer.

Все эти методы возвращают promise, который в конечном итоге выполняется и выводит содержимое.

Например, прочитаем ответ как строку и выведем её в элемент с id="result":

JavaScript
<button type="button" id="get-text">Получить текст с сервера</button>
<div id="result"></div>

<script>
  document.querySelector('#get-text').onclick = () => {
    fetch('/examples/ajax/01.html')
      .then(response => {
        if (response.ok) {
          response.text().then(data => {
            // выведем данные в #result
            document.querySelector('#result').innerHTML = data;
          });
        }
      })
      .catch(error => {
        console.log(error);
      });
  }
</script>

Вместо обещаний можно использовать async-await:

JavaScript
<button type="button" id="get-text">Получить текст с сервера</button>
<div id="result"></div>

<script>
  document.querySelector('#get-text').onclick = async () => {
    try {
      let response = await fetch('/examples/ajax/01.html');
      if (response.ok) {
        let data = await response.text();
        // выведем данные в #result
        document.querySelector('#result').innerHTML = data;
      }
    }
    catch (error) {
      console.log(error);
    }
  }
</script>

Пример использования fetch() для получения JSON

Напишем пример, в котором будем получать информацию о пользователях в формате JSON. Для того, чтобы запросить данные об конкретном пользователе будем брать значение из поля, а затем добавлять его в URL посредством GET-параметра id.

Пример использования fetch() для получения JSON

HTML-код:

HTML
<input type="text" id="user-id" value="1">
<button type="button" id="get-user">Получить user по id</button>
<button type="button" id="get-users">Вывести всех</button>
<div id="result"></div>

<script>
  // функция для получения данных с сервера и вывода их на страницу
  const getUsers = async (id = -1) => {
    try {
      // использование метода fetch() для отправки асинхронного запроса на сервер
      let response = await fetch(`/examples/ajax/05.php?id=${id}`);
      if (response.ok) {
        // получаем ответ в формате JSON и сохраняем его в data
        let data = await response.json();
        // выполняем рендеринг полученных данных в элемент #result
        const count = data['count'];
        let html = `<div style="margin-bottom: 10px;">Найдено: ${count}</div>`;
        for (const key in data['data']) {
          const user = data['data'][key];
          html += `<div style="background-color: #fafafa; display: flex; border-radius: 5px; overflow: hidden; margin-bottom: 10px;">
              <img src="${user['avatar_url']}" alt="${user['name']}">
              <ul>
                <li>Имя: ${user['name']}</li>
                <li>Email: ${user['email']}</li>
                <li>Страна: ${user['location']}</li>
              </ul>
            </div>`;
        }
        // выведем данные в #result
        document.querySelector('#result').innerHTML = html;
      }
    }
    catch (error) {
      console.log(error);
    }
  }
  // при клике на #get-user
  document.querySelector('#get-user').onclick = () => {
    const id = parseInt(document.querySelector('#user-id').value);
    // вызовем функцию getUser и передадим ей id пользователя который нужно получить
    getUsers(id);
  }

  // при клике на #get-users
  document.querySelector('#get-users').onclick = () => {
    // вызовем функцию getUser
    getUsers();
  }
</script>

Содержимое файла «05.php», который возвращает данные о пользователях в формате JSON:

PHP
<?php

$id = isset($_GET['id']) ? (int) ($_GET['id']) : -1;

$users = [
  1 => [
    'avatar_url' => '/examples/ajax/img-01.jpg',
    'name' => 'Александр Мухин',
    'email' => 'alexander@mail.ru',
    'location' => 'Россия'
  ],
  2 => [
    'avatar_url' => '/examples/ajax/img-02.jpg',
    'name' => 'Евгений Смирнов',
    'email' => 'evgeniy@gmail.com',
    'location' => 'Украина'
  ],
  3 => [
    'avatar_url' => '/examples/ajax/img-03.jpg',
    'name' => 'Ольга Соколова',
    'email' => 'olga@yandex.ru',
    'location' => 'Россия'
  ]
];

$output = ['count' => 0, 'data' => []];

if ($id > 0 & $id <= count($users)) {
  $output['count'] = 1;
  $output['data'] = [$id => $users[$id]];
} elseif ($id == -1) {
  $output['count'] = count($users);
  $output['data'] = $users;
}

echo json_encode($output);

Пример простого запроса HTTP POST

HTML
<style>
  .form-wrapper {
    position: relative;
  }

  .result {
    display: none;
  }

  .form-success .result {
    display: flex;
  }
</style>

<div class="form-wrapper">
  <form action="/examples/ajax/06.php" method="post" id="user">
    <div class="form-group">
      <label for="name">Имя</label>
      <input type="text" name="name" id="name" value="">
      <div class="error"></div>
    </div>
    <div class="form-group">
      <label for="email">Email</label>
      <input type="email" name="email" id="email" value="">
      <div class="error"></div>
    </div>
    <button type="submit">Отправить</button>
  </form>
  <div class="result">
    <div class="result__text">Форма успешно отправлена!</div>
    <div class="result_ok"></div>
    <button type="button" class="result__close">Ok</button>
  </div>
</div>

<script>
const sendUser = async () => {
  try {
    let response = await fetch(document.forms.user.action, {
      method: 'post',
      body: new FormData(document.forms.user)
    });
    if (response.ok) {
      let result = await response.json();
      document.forms.user.querySelectorAll('.error').forEach(el => {
        el.textContent = '';
      })
      if (result['result'] === 'error') {
        const errors = result['error'];
        for (const [key, value] of Object.entries(errors)) {
          document.forms.user.querySelector(`[name="${key}"]`).nextElementSibling.textContent = value;
        }
      } else {
        document.forms.user.reset();
        document.forms.user.closest('.form-wrapper').classList.add('form-success');
      }
    }
  }
  catch (error) {
    console.log(error);
  }
}

document.forms.user.onsubmit = (e) => {
  e.preventDefault();
  sendUser();
}

document.querySelector('.result__close').onclick = (e) => {
  e.target.closest('.form-wrapper').classList.toggle('form-success');
}
</script>

Содержимое php-файла:

PHP
<?php

$result = ['result' => 'success', 'error' => []];

$name = '';
$email = '';

if (empty($_POST['name'])) {
  $result['result'] = 'error';
  $result['error']['name'] = 'Это поле не заполнено!';
} else {
  $name = $_POST['name'];
  if (!preg_match("/^[a-zа-яё\s]+$/iu", $name)) {
    $result['result'] = 'error';
    $result['error']['name'] = 'Имя содержит недопустимые символы!';
  }
}

if (empty($_POST['email'])) {
  $result['result'] = 'error';
  $result['error']['email'] = 'Это поле не заполнено!';
} else {
  $email = $_POST['email'];
  if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $result['result'] = 'error';
    $result['error']['email'] = 'Поле email заполнено некорректно!';
  }
}

$data = [];

if (file_exists('users.txt')) {
  $data = json_decode(file_get_contents('users.txt'), true);
}

if (array_key_exists($email, $data)) {
  $result['result'] = 'error';
  $result['error']['email'] = 'Пользователь с таким email уже существует!';
} else {
  $data[$email] = $name;
}

if ($result['result'] == 'success') {
  if (file_put_contents('users.txt', json_encode($data), LOCK_EX) == false) {
    $result['result'] = 'error';
  }
}

echo json_encode($result);

Вторая часть - получение данных:

HTML
<div class="users">
  <button type="button" id="get-users">Получить список пользователей</button>
  <table>
    <thead>
      <tr>
        <th>#</th>
        <th>Имя</th>
        <th>Email</th>
      </tr>
    </thead>
    <tbody></tbody>
  </table>
</div>

<script>
document.querySelector('#get-users').onclick = async () => {
  try {
    let response = await fetch('/examples/ajax/07.php');
    if (response.ok) {
      let result = await response.json();
      const table = document.querySelector('.users tbody');
      let html = '';
      let i = 1;
      for (const [key, value] of Object.entries(result)) {
        html += `<tr><td>${i++}</td><td>${value}</td><td>${key}</td></tr>`;
      }
      table.innerHTML = html;
    }
  }
  catch (error) {
    console.log(error);
  }
}
</script>

Содержимое php-файла:

PHP
<?php

$output = [];

if (file_exists('users.txt')) {
  $output = json_decode(file_get_contents('users.txt'), true);
}

echo json_encode($output);

Комментарии: 7

Serhii
Serhii

Добрый день, Александр.

Спасибо за познавательный и интересный контент. Подскажите пожалуйста, как правильно, в скрипте настроить запрос fetch, чтобы получить динамическое значение элемента (nodeValue) на удаленном сайте? Сайт, отдает данные в формате text/html и в результате преобразования

let data = await response.text();

необходимых данных нет. Но в девтулзах, во вкладке Properties, эти значения отображаются. Спасибо.
Александр Мальцев
Александр Мальцев

Добрый день! А что у вас в переменной data? Если там текст страницы, то используйте регулярку чтобы найти в нём нужное значение элемента.

Serhii
Serhii

Добрый день.

Спасибо, что ответили.

Да, в переменной дата - текст страницы, но не полный.

Собственно, если подробнее о задаче.

Это тестовая страница, после перехода на нее, через рандомный промежуток времени, на странице запускается php-скрипт, который публикует в текстовом блоке, объект с тремя свойствами. По условию, нужно получить значение 3-го свойства и вывести его в JSON.

https://dev.amidstyle.com/

Я еще только учусь и с юзкейсами такого плана не встречался, как их корректно решать не знаю.

Получается на момент действия моего скрипта, и обращения странице через fetch, указаный php-скрипт, еще не отрабатывает и в текстовом блоке, данные еще не опубликованы.

Возможно, я изначально не корректным способом пытаюсь решить данный тест?...

Данный тест из области web-scrapinga, но интересно, решить ванильным JS, без использования библиотек для парсинга.

Спасибо, если сможете подсказать правильный метод или способ решения.
Александр Мальцев
Александр Мальцев

С помощью fetch как только сервер отдаст ответ вы его сразу получите. Ничего дальше уже на сервере выполняться не будет. Если хотите получить полный ответ с помощью fetch, то отдавайте его только после того, когда он будет готов полностью.

Александр
Александр
А нет не снят.
Но вопрос не в том, чтобы прогресс сделать.
Есть задача отправить массив именно поэлементно (тяжелый массив c картинками в base64), дожидаясь окончания обработки каждого элемента php перед отправкой следующего элемента.
Вот и вопрос, как дожидаться ответа php скрипта, а потом снова выполнить тот же fetch
Александр
Александр
Александр, приветствую.

Отличная статья.

Столкнулся с проблемой при попытке сделать отображение прогресса обработки массива PHP обработчиком.
Например, имеем массив:

	var data = [
		{
			itemIndex:1,
			itemName:'Раз'
		},
		{
			itemIndex:2,
			itemName:'Два'
		},
		{
			itemIndex:3,
			itemName:'Три'
		},
		
		.........
		
		{
			itemIndex:100,
			itemName:'Сто'
		}
		
	]
Вроде решение — которое лежит на поверхности — в цикле отправлять
элементы массива в JSON формате PHP обработчику, пока обработчик чего-то там делает — ждать ответа,
и в случае ответа про «все хорошо» — увеличить прогресс.

Однако, например Google Chrome говорит:

JavaScript console.log causes error: “Synchronous XMLHttpRequest on the main thread is deprecated…”

При чем раз-на-раз не приходится. Когда-то срабатывает, когда-то — выдает ошибку в консоль.

Если делать это асинхронными запросами, то (условно) прогресс стоит на месте до полной обработки
всех элементов и только потом разом стает 100%.

Подскажите. Есть-ли какой-то способ решить эту проблему?
Александр
Александр
Вопрос снят. Решен сильно проще чем циклическая отправка данных.