Полный цикл в digital

Fetch

Fetch API предоставляет упрощенный и в тоже время гибкий и мощный инструмент для обращения к сетевым ресурсам по сравнению со стандартным XMLHttpRequest. Ключевым элементом этого Fetch API является функция fetch(). Она реализована в различных контекстах. В частности, в браузере она реализована интерфейсом Windows.

Функция fetch имеет следующий синтаксис:

let fetchPromise = fetch(url, options)
  1. url URL для отправки запроса
  2. options дополнительные параметры - метод, заголовки и так далее

Без options это простой GET-запрос, скачивающий содержимое по адресу url. Функция fetch() возвращает объект Promise, который получает ответ после завершения запроса к сетевому ресурсу. promise выполняется с объектом встроенного класса Response в качестве результата, как только сервер пришлёт заголовки ответа.

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

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

let response = fetch(url);
index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      button.addEventListener("click", function () {
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html");
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

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

  • body содержимое ответа в виде объекта ReadableStream
  • bodyUsed хранит булевое значение, которое указывает, было ли содержимое ответа уже использовано.
  • headers набор заголовков ответа в виде объекта Headers
  • ok хранит булевое значение, которое указывает, завершился ли запрос успешно (то есть если статусной код ответа находится в диапазоне 200-299)
  • redirected хранит булевое значение, которое указывает, является ли ответ результатом переадресации
  • status хранит статусный код ответа
  • statusText хранит сообщение статуса, которое соответствует статусному коду
  • type хранит тип ответа
  • url хранит адрес URL. Если в процессе запроса происходит ряд переадресаций, то хранит конечный адрес URL после всех переадресаций

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

  • response.text() читает ответ и возвращает как обычный текст,
  • response.json() декодирует ответ в формате JSON
  • response.formData() возвращает ответ как объект FormData
  • response.blob() возвращает объект как Blob (бинарные данные с типом)
  • response.arrayBuffer() – возвращает ответ как ArrayBuffer (низкоуровневое представление бинарных данных)
  • response.body это объект ReadableStream, с помощью которого можно считывать тело запроса по частям

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

Ключевое слово await заставит интерпретатор JavaScript ждать до тех пор, пока промис справа от await не выполнится. После чего оно вернёт его результат, и выполнение кода продолжится.

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      // функция, возвращающая промис
      function promis(ms) {
        return new Promise((resolve) =>
          setTimeout(() => {
            resolve(console.log(2));
          }, ms)
        );
      }
      button.addEventListener("click", async function () {
        console.log(1);
        try {
          // запрос к файлу ajax.html
          let response = await fetch("/ajax.html");
          // получаем информацию о запросе и в каком формате мы получим ответ
          if (response.ok) {
            // получаем тело ответа
            let data = await response.text();
            // ждем выполнения промиса
            await await promis(1000);
            // выведем данные
            div.innerHTML = data;
          }
        } catch (error) {
          console.log(error);
        }
        console.log(3);
      });
    </script>
  </body>
</html>

Заголовки ответа

Заголовки ответа хранятся в похожем на Map объекте response.headers.

Это не совсем Map, но мы можем использовать такие же методы, как с Map, чтобы получить заголовок по его имени:

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      button.addEventListener("click", function () {
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html");
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // выводим в консоль заголовок
              console.log(response.headers.get("Content-Type"));
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

Можем перебрать заголовки в цикле:

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      button.addEventListener("click", function () {
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html");
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // выводим в консоль заголовки
              for (let [key, value] of response.headers) {
                console.log(`${key} = ${value}`);
              }
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

Для получения данных из заголовков мы можем воспользоваться один из следующих методов интерфейса Headers:

  • entries() возвращает итератор, который позволяет пройтись по всем заголовкам
  • forEach() осуществляет перебор заголовков
  • get() возвращает значение определенного заголовка
  • has() возвращает true, если установлен определенный заголовок
  • keys() получает все названия установленных заголовков
  • values() получает все значения установленных заголовков

Заголовки запроса

Для установки заголовка запроса в fetch мы можем использовать опцию headers. Она содержит объект с исходящими заголовками, например:

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      button.addEventListener("click", function () {
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html", {
          // устанавливаем заголовок
          headers: {
            Test: "Test",
          },
        });
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

Есть список запрещённых HTTP-заголовков, которые мы не можем установить:

  • Accept-Charset, Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie, Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via
  • Proxy-*
  • Sec-*

Отправка данных в запросе

Для отправки данных в запросе в функции fetch() применяется в рамках второго параметра применяется опция body. Стоит учитывать, что в запросах с методом GET и HEAD для запроса нельзя установить эту опцию. Эти данные могут представлять типы:

  • Blob
  • BufferSource
  • FormData
  • URLSearchParams
  • USVString
  • ReadableStream

Определим на странице index.html код для отправки данных на сервер. Для отправки применяется метод POST. В качестве отправляемых данных выступает простая строка Test:

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      let login = "test";
      let pass = 123123;
      button.addEventListener("click", function () {
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html", {
          // устанавливаем заголовок
          headers: {
            Test: "Test",
          },
          // устанавливаем метод
          method: "POST",
          // передаем тело запроса
          body: `'login=' + ${login} + '&pass=' + ${pass}`,
        });
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

Часто не очень удобно формировать вручную свойство body. В него можно передать объекты класса URLSearchParams, в которых содержатся переменные и их значения:

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      let login = "test";
      let pass = 123123;
      button.addEventListener("click", function () {
        // конструктор который передает POST параметры в body
        let params = new URLSearchParams();
        params.set("login", login);
        params.set("pass", pass);
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html", {
          // устанавливаем заголовок
          headers: {
            Test: "Test",
          },
          // устанавливаем метод
          method: "POST",
          // передаем тело запроса
          body: params,
        });
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

Для кодирования данных метод FormData использует формат "multipart/form-data". Это означает то, что он позволяет подготовить для отправки по AJAX не только текстовые данные, но и файлы (input с type, равным file):

index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <form method="post">
      <input type="text" name="user_name" />
      <input type="tel" name="user_phone" />
      <button>Нажми</button>
    </form>
    <div></div>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      let form = document.querySelector("form");
      button.addEventListener("click", function (event) {
        // отменяем стандартное действие
        event.preventDefault();
        // конструктор который создает объект формы
        let params = new FormData(form);
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html", {
          // устанавливаем заголовок
          headers: {
            Test: "Test",
          },
          // устанавливаем метод
          method: "POST",
          // передаем тело запроса
          body: params,
        });
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>

Передача куки

По умолчанию куки не передаются в AJAX запросах. Это означает, что не будет работать сессия сервера. Обычно передача кук нам все-таки нужна. Ее можно включить настройкой credentials у которой есть три параметра:

  1. credentials: 'include' заставит передавать куки в AJAX запросе даже если запрос cross-origin
  2. credentials: 'same-origin' заставит передавать куки, но только на тот сайт, на котором запускается скрипт
  3. credentials: 'omit' запрещает передавать куки
index.html<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>hmarketing.ru</title>
  </head>
  <body>
    <div></div>
    <button>Нажми</button>
    <script>
      let div = document.querySelector("div");
      let button = document.querySelector("button");
      button.addEventListener("click", function () {
        // запрос к файлу ajax.html
        let response = fetch("/ajax.html", {
          // запрещаем передавать куки
          credentials: "omit",
        });
        response
          // в первом then находится информация о запросе и в каком формате мы получим ответ
          .then((response) => {
            if (response.ok) {
              // возвращаем тело ответа
              return response.text();
            }
          })
          // в втором then находится ответ на запрос
          .then((text) => {
            // выведем данные
            div.innerHTML = text;
          })
          // в catch мы попадём когда fetch() вообще не может выполнить запрос, т.е. нет такого сайта или произошла потеря сетевого соединения
          .catch((error) => {
            console.log(error);
          });
      });
    </script>
  </body>
</html>
Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг