Использование JavaScript Fetch API для получения данных

Андрей Худотеплый

Fetch() позволяет делать запросы, схожие с XMLHttpRequest (XHR). Основное отличие (оно же преимущество) заключается в том, что Fetch API использует Promises (промисы), которые позволяют использовать более простое и чистое API, избегать катастрофического количества callback’ов и необходимости помнить API для XMLHttpRequest. Так давайте же пощупаем в этой статье этот славный Fetch API на предмет основ его применения.

Содержание:
1. Что такое JavaScript Fetch API?
2. Как и где применять fetch()
3. GET/POST
4. Работаем с данными легко
5. Основные отличия fetch от jQuery.ajax()
6. Требования, совместимость и дань первопроходцам

1. Что такое JavaScript Fetch API?

Самым распространенным протоколом обмена информацией между веб-сайтом и сервером является http(s). Это значит, что с самого начала возникновения браузеров в них встроены модули, задачей которых является прием и отправка информации через http-запросы. В статье об AJAX мы рассматривали, как можно получать и отправлять данные через XMLHttpRequest, который является самым старым и совместимым со всеми браузерами стандартом.

Однако там же мы разобрали, почему далеко не всегда удобно использовать низкоуровневые функции. Также со времен ранних стандартов JavaScript в язык добавилось много конструкций, облегчающих как написание, так и поддержку кода.

Например, использование промисов и async/await-команд упрощает контролировать параллелизм, значительно облегчает читабельность и во многих случаях упрощает обработку ошибок.

Потому логично, что в браузерах и стандартах webAPI появились новые интерфейсы, упрощающие контроль коммуникации браузеров с серверами. Таким новым стандартом стал Fetch API. Это своего рода высокоуровневая обертка вокруг XMLHttpRequest, потому с точки зрения http-протокола ничего нового не появилось, но работать с ним теперь стало значительно удобнее.

Пример кода №1:

let myRequest = new Request('www.example.com');
 
fetch(myRequest)
   .then(response => {
       if (!response.ok)
           throw new Error(`HTTP error! status: ${response.status}`);
       return response.text();
   })
   .then(response => console.log(response))
   .catch(error => console.log(error));

2. Как и где применять fetch()

Основная цель fetch — принимать и передавать данные между страницей браузера и веб-сервером. И если вам не надо ориентироваться на старые версии браузеров или по какой-то другой причине спускаться на уровень XMLHttpRequest и колбеков, то скорее всего вы останетесь довольны использованием fetch.

Ведь кроме конструкций Promise -> then можно в полной мере пользоваться гибкостью и преимуществами async/await. Так, пример выше можно легко переписать и расширить (Пример №2):

(async function load (){
   try {
       let res1 = await fetch(new Request('www.example.com/1'));
       let res2 = await fetch(new Request('www.example.com/2'));
       return [res1, res2];
   }
   catch(err) { console.log('err:', err); }
})();

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

Синтаксис fetch() предельно прост:

const fetchResponsePromise = fetch(resource [, init])

где resource — это либо строка с url, либо объект, превращаемый в строку, либо инстанс (объект) Request, а init — это необязательные параметры запроса.

Но есть одно существенное отличие от XMLHttpRequest в том, как далее отслеживать созданные запросы. Если в XMLHttpRequest мы при создании запроса получали объект, к которому далее можно обращаться, то результат fetch() — это промис, который вовсе не объект Request или Response, что усложняет прерывание запросов.

Если с XMLHttpRequest прервать запрос очень просто (Пример №4):

const xhr = new XMLHttpRequest();
xhr.open('GET', 'www.example.com', true);
xhr.send();
setTimeout(() => { xhr.abort() }, 100); // прервать запрос через 100 мс

то в Fetch API для этих целей создан специальный объект — AbortController, посредством которого можно сгенерировать объект AbortSignal, через который и можно прервать запрос.

Так, пример №4, но с fetch, выглядит (Пример №5):

const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => { controller.abort() }, 100);
fetch('www.example.com', { signal }).then(...).catch(...);

3. GET/POST

Все примеры выше были get-запросами, поскольку этот метод ожидаемо установлен по умолчанию. Чтобы выбрать тип запроса, его надо указать либо в параметрах init, либо при создании объекта Request, не забывая при этом, что отправляемые данные в body требуют указания их типа (Пример №3):

(async () => {
   try {
       const response = await fetch('www.example.com', {
           method: 'POST',
           headers: {
               'Accept': 'application/json',
               'Content-Type': 'application/json'
           },
           body: JSON.stringify({
               param: 'значение'
           })
       });
       const content = await response.json();
   }
   catch(err) { console.log('err:', err); }
})();

обратите внимание на предпоследнюю строку с response.json(). Она вернет уже готовый объект данных, предварительно сконвертировав полученную строку.

4. Работаем с данными легко

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

и свойство Response.body (которое содержит полученные данные) имеет тип ReadableStream. То есть через Fetch API вы можете легко передать, например, аудиопоток. Пример здесь.

Удобство подхода в том, что вы также можете перехватывать ошибки конвертации данных, например, как в примере №3, если возвращаемая строка не может быть преобразована в объект.

Если вы внимательно посмотрите на классы Fetch API, то заметите, что методы форматирования данных содержит также и Request.

Но они не работают на подготовку отправляемых данных, а скорее дублируют вызов fetch() с одновременной конвертацией данных в нужный тип.

const request = new Request('www.example.com');
request.json().then(data => {
   // тут обрабатываем данные
});

Обратите внимание, что в конфигурации отсутствует хедер с типом данных. Он будет установлен автоматически.

Я не буду останавливаться на примерах с каждым типом данных, поскольку тут нет ничего нового. А вот упомянуть имплементацию особых дополнительных данных можно.

Например, в ссылках CDN часто используется Subresource Integrity (SRI) в виде числа хеш-функции от передаваемых данных. В Fetch API доступ к этому полю очень прост: Request.integrity

5. Основные отличия fetch от jQuery.ajax()

Глупо предполагать, что Fetch API появился на ровном месте. До его появления многие разработчики нарабатывали свои обертки XMLHttpRequest, лучшие из которые потом становились частью фреймворков или отдельными библиотеками. Пожалуй, самой распространенной библиотекой, в которой реализованы идеи, близкие к Fetch API, стал jQuery с его методами .ajax.

Но поскольку jQuery обременена совместимостью с ранними своими версиями, которые основывались на колбеках, ее подход к обработке запросов не был включен в стандарт Fetch API. Более того, в Fetch API стандартизированы объекты данных через новые классы — Request и Response, а jQuery использует объект jqXHR, который, по сути, есть расширенный XMLHttpRequest.

Описанный выше вызов с jQuery будет выглядеть так:

$.get('www.example.com')
   .done(data => console.log(data))
   .fail(jqXHR => console.error(`HTTP error: ${jqXHR.statusText}`));

Вид почти одинаковый, но ни .done, ни .fail в методах стандартного Promise нет.

На этом различия не заканчиваются. Если вы заметили, наш пример №1 в обработке полученных данных fetch содержит проверку статуса результата, а в jQuery ее нет. Сделано это затем, что fetch не триггерит состояние ошибки, если запрос вернул какой-то http-статус в принципе. То есть catch не поймает ошибку, если запрос вернет статус 404 или 500, например.

Потому мы искусственно бросаем Error, чтобы передать ее в обработчик. В jQuery такого делать не надо, поскольку статусы, отличные от 200, будут переданы в обработчик ошибок по умолчанию.

Но вот код, как в примере №2, с jQuery не прокатит.

Также есть различия в типах данных, которые вы можете получить «из коробки». jQuery ориентирована на работу с текстом и json, а потому не умеет самостоятельно работать с FormData, BufferSource, ReadableStream и Blob. Зато у нее есть поддержка JSONP и возможность подключать предварительные фильтры данных (dataFilter).

Еще одно существенное отличие в том, что fetch не передает куки по умолчанию (вследствие новых требований к обработке персональной информации веб-сайтами), если вы не укажете это явно в опциях credentials:

fetch(url, {
   credentials: "same-origin" // or "include"
}).then(...).catch(...);

(NOTE: с 2017-го года некоторые браузеры ставят опцию same-origin по умолчанию)

6. Требования, совместимость и дань первопроходцам

Поскольку Fetch API — это достаточно новая часть API браузера, перед его использованием необходимо убедиться, что браузер имеет поддержку всех используемых компонент, поскольку они вводились не сразу и до сих пор развиваются и модернизируются.

Потому надо надо проверять доступность методов для:

И если, например, fetch() появился в Chrome с 42-й версии, то поддержка .formData только с 60-й.

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

В MDN экспериментальный метод обозначен значком колбы: 


И в заключение — главное не путать Fetch API браузера и fetch в Nodejs. Это совершенно разные звери, и гуглить и читать с учетом этого нужно более осторожно.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023