Использование 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. Это совершенно разные звери, и гуглить и читать с учетом этого нужно более осторожно.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: