Fetch | Как получить данные? Используем JavaScript

Fetch | Как получить данные? Используем JavaScript

 

В этой публикации мы постараемся разобраться в том, как дотянуться до заветных данных при помощи JavaScript и метода fetch(input, init)

Мы по шагам будем погружаться в Выборку(Fetch) и смотреть полученные итерации на каждом шаге, чтобы не запутаться и не пропустить важное.

 

Видео

 

Задача № 1

Мы хотим получить всю HTML-разметку страницы с сайта интернет магазина.

 

Немножко теории

Стандарт Fetch определяет запросы, ответы и процесс, который их связывает: выборка (fetching). Стандарт Fetch также определяет JavaScript API fetch(), который предоставляет большую часть сетевых функций на довольно низком уровне абстракции.

Заголовок запроса `Origin` указывает, откуда происходит выборка. Заголовок `Origin` — это версия заголовка`Referer` [sic], которая не показывает путь. Он используется для всех HTTP-выборок, чей ответ на запрос поврежден «cors», а также для тех, у которых метод запроса не является ни GET, ни HEAD. Из-за ограничений совместимости он не входит во все выборки.

 

Протокол CORS существует, чтобы разрешить обмен ответами из разных источников и более гибкую выборку, чем это возможно с помощью элемента формы HTML <form>. Он расположен поверх HTTP и позволяет в ответах объявлять, что они могут быть переданы другим источникам. Протокол CORS состоит из набора заголовков, которые указывают, может ли ответ быть общим для разных источников.

Блокировка чтения из разных источников (Cross-origin read blocking), более известная как CORB, представляет собой алгоритм, который выявляет сомнительные выборки ресурсов из разных источников (например, выборки, которые в любом случае не удались бы, как попытки отрисовки JSON внутри элемента img) и блокирует их до того, как они достигнут веб-страницы. CORB снижает риск утечки конфиденциальных данных, удерживая их подальше от веб-страниц с разными источниками.

Метод fetch() — это относительно низкоуровневый API для выборки ресурсов. Он охватывает немного больше вопросов, чем XMLHttpRequest, хотя в настоящее время его не хватает, когда дело доходит до последовательности запросов (а не ответов).

 

Вывод из теории

Если по-простому, то мы не можем получить данные, если отправляем запросы с другого источника. Нам нужно «прикинуться» браузером, получить документ и уже из этого документа отправить запрос на тот же домен.

 

Решение

Мы потренируемся получать данные на примере интернет-магазина. Откроем страницу в браузере:

https://www.ozon.ru/category/besprovodnye-pylesosy-10657/

Откроем «Инструменты разработчика» (CTRL+SHIFT+i) и перейдём на вкладку «Console«.

 

CORB - Fetch - JavaScript
CORB — Fetch — JavaScript

И вот мы сразу видим блокировку чтения из разных источников. То есть сами разработчики этого сайта не могут получить данные со своего другого поддомена. Из источника «www.ozon.ru» нельзя получить данные другого источника «xapi.ozon.ru«. Мы не ввели ни одного запроса, но уже столкнулись с работой CORB.

«cross-origin» или по-русски «перекрестное происхождение» мешает нам получать данные из других источников.

 

В нашем случае мы будем работать в зоне «www.ozon.ru» и посылать запросы из браузера в эту же зону при помощи метода fetch(). Например, будем слать запрос на:

https://www.ozon.ru/context/detail/id/148279648/

Первый вид команды будет выглядеть так:

fetch("https://www.ozon.ru/context/detail/id/148279648/")

Мы вызвали функцию fetch() и передали в качестве одного аргумента текстовое значение нужной нам страницы (её адрес). Той страницы, с которой мы хотим получить разметку, не загружая её в новой вкладке.

Fetch-запрос с одним параметром - JavaScript
Fetch-запрос с одним параметром — JavaScript

 

Функция вернула нам объект Обещания Promise со статусом fulfilled. У Обещаний может быть всего три статуса:

  1.  fulfilled — (выполнено)
  2. rejected — (отклонено)
  3. pending — (ожидает выполнения)

То есть объект Promise всегда находится в каком-либо из этих состояний.

В нашем случае мы успешно выполнили запрос на нужный адрес (статус fulfilled). Результат работы Обещания вернул нам экземпляр объекта Response. Стандарт Fetch имеет специальный класс Response(интерфейс Response). Давайте посмотрим, как выглядит экземпляр объекта Response.

Мы получили «успешное» Обещание и теперь нам нужно сделать следующий шаг, который символизирует «успех». В объектах Promise мы должны вызвать метод then() и передать в него результат «успеха» (экземпляр объекта Response на первой итерации then). В качестве обратного вызова мы укажем console.log(), чтобы увидеть результат в консоли браузера. Мы выведем условную выдуманную переменную resp

fetch("https://www.ozon.ru/context/detail/id/148279648/").then(resp => {console.log(resp)})
Вывод в консоль Response - JavaScript
Вывод в консоль Response — JavaScript

Сейчас мы видим сам экземпляр объекта Response, его ключи и их значения. Так где же данные? Где же лежит наш заветный код страницы?

Статус 200 в прототипе объекта Response - JavaScript
Статус 200 в экземпляре объекта Response — JavaScript

Теперь, когда мы получили экземпляр объекта Response со статусом 200 мы можем получить ТЕЛО ОТВЕТА. В стандарте Fetch за тело ответа отвечает  интерфейс «mixin Body«.

Интерфейс mixin Body - Fetch - JavaScript
Интерфейс mixin Body — Fetch — JavaScript

У него есть 5 вариантов «распознавания» тела ответа, которые прилетают с экземпляром объекта Response:

  1. arrayBuffer()
  2. blob()
  3. formData()
  4. json()
  5. text()

 

Нас будет интересовать пятый вариант — text(), который просто вернёт нам строковый тип данных из тела ответа. Это будет «успех» и теперь тело ответа мы вернём в следующую итерацию then(). Во втором then мы выведем в консоль тело ответа. (уже НЕ экземпляр объекта Response). Переменную resBody мы также выдумаем из воздуха. Можно называть их как угодно, лишь бы вам было понятно, что они означают.

fetch("https://www.ozon.ru/context/detail/id/148279648/")
   .then(resp => {return resp.text()})
   .then(resBody => {console.log(resBody)})
Тело ответа Fetch - JavaScript
Тело ответа Fetch — JavaScript

И мы видим в консоли разметку запрашиваемой страницы — тело ответа. Отлично. Мы уже близко.

Чтобы начать взаимодействовать с разметкой, нужно заранее создать переменную до вызова функции fetch(). То есть сначала у нас будет переменная, в которую мы будем складывать разметку и только потом мы вызываем fetch(), внутри которого меняем значение этой переменной.

Для примера создадим переменную razmetka:

var razmetka = ""

Это будет пустая строка.

Вызовем fetch():

fetch("https://www.ozon.ru/context/detail/id/148279648/")
   .then(resp => {return resp.text()})
   .then(resBody => {razmetka = resBody})
Тело ответа в переменной - Fetch - JavaScript
Тело ответа в переменной — Fetch — JavaScript

Важно учитывать, что Fetch является асинхронной операцией т. к. мы не можем знать когда ответит сервер или ответит вообще. Нужно заранее объявлять видимые переменные (глобальные), чтобы они создались при синхронной загрузке кода интерпретатором.

После выборки переменная razmetka получает новое значение. Теперь в переменной лежит разметка другой страницы сайта, а значит мы получили данные при при помощи функции fetch()

 

Задача № 2 (альтернативный пример)

Нужно получить простую HTML-разметку из файла text.html. Нужно поместить разметку в текущий документ, открытый в браузере пользователя. Получаемая разметка такая:

<h1>Заголовок публикации</h1>
<p>Текст под заголовком</p>

Документ пользователя содержит только один заголовок на странице:

<h1>Fetch | Шаг 1</h1>

Шаг 1 — Смотрим, что возвращает метод fetch() с одним аргументом

var a = fetch("text.html")

В нашей простой задаче мы передаём в качестве аргумента строку, которая указывает на местоположение документа на сервере.

Объект Promise
Объект Promise

Результатом выполнения выборки Fetch является обещание Promise.

 

Шаг 2 — Достаём из обещания ответ

Теперь мы можем работать с обещанием и создавать цепочку последовательных операций. Давайте поглядим о каком [[PromiseResult]] идёт речь. В метод then() я передам созвучное название «promiseResult«.

var a = fetch("text.html")
   .then(promiseResult => {console.log(promiseResult)})

Стрелочной функцией мы выводим в консоль успешный результат обещания. На этот раз мы получаем объект Response.

Объект Response из объекта Promise
Объект Response из объекта Promise

У этого объекта есть свои интересные свойства. Но мы идём дальше т. к. пока того, что нам нужно, не видно.

 

Шаг 3 — Достаём из ответа данные

Отобразим содержимое объекта Response в консоли браузера, как и делали ранее. Воспользуемся методом ответов Response, который именуется text()

var a = fetch("text.html")
   .then(promiseResult => {
      console.log(promiseResult)
      return promiseResult.text()
   })
   .then(responseResult => {console.log(responseResult)})
Прочитали содержимое объекта Response
Прочитали содержимое объекта Response

Мы получили содержимое файла text.html и вывели его в консоль браузера.

 

Шаг 4 — Публикация содержимого из файла на страницу

Теперь логичнее всего завершить начатое — это опубликовать ответ на страницу. Воспользуемся простой конструкцией «document.write(responseResult)«:

var a = fetch("text.html")
   .then(promiseResult => {
      console.log(promiseResult)
      return promiseResult.text()
   })
   .then(responseResult => {
      console.log(responseResult)
      document.write(responseResult)
   })
Итоговая HTML-разметка
Итоговая HTML-разметка

Готово.

 

Итог. Что мы делали?

Fetch возвращает объект Promise, из Promise получаем объект Response, а из Response достаём нужное нам содержимое файла — выбираем нужный метод.

 

Ссылки для информации

Стандарт Fetch на русском языке — https://efim360.ru/fetch/

Оригинальная версия стандарта Fetch на английском языке — https://fetch.spec.whatwg.org/