Fetch
JavaScript может отправлять сетевые запросы на сервер и подгружать новую информацию по мере необходимости.
Например, мы можем использовать сетевой запрос, чтобы:
- Отправить заказ,
- Загрузить информацию о пользователе,
- Запросить последние обновления с сервера,
- …и т.п.
Для сетевых запросов из JavaScript есть широко известный термин «AJAX» (аббревиатура от Asynchronous JavaScript And XML). XML мы использовать не обязаны, просто термин старый, поэтому в нём есть это слово. Возможно, вы его уже где-то слышали.
Есть несколько способов делать сетевые запросы и получать информацию с сервера.
Метод fetch() — современный и очень мощный, поэтому начнём с него. Он не поддерживается старыми (можно использовать полифил), но поддерживается всеми современными браузерами.
let promise = fetch(url, [options])
- url – URL для отправки запроса.
- options – дополнительные параметры: метод, заголовки и так далее.
Без options это простой GET-запрос, скачивающий содержимое по адресу url .
Браузер сразу же начинает запрос и возвращает промис, который внешний код использует для получения результата.
Процесс получения ответа обычно происходит в два этапа.
Во-первых, promise выполняется с объектом встроенного класса Response в качестве результата, как только сервер пришлёт заголовки ответа.
На этом этапе мы можем проверить статус HTTP-запроса и определить, выполнился ли он успешно, а также посмотреть заголовки, но пока без тела ответа.
Промис завершается с ошибкой, если fetch не смог выполнить HTTP-запрос, например при ошибке сети или если нет такого сайта. HTTP-статусы 404 и 500 не являются ошибкой.
Мы можем увидеть HTTP-статус в свойствах ответа:
- status – код статуса HTTP-запроса, например 200.
- ok – логическое значение: будет true , если код HTTP-статуса в диапазоне 200-299.
let response = await fetch(url); if (response.ok) < // если HTTP-статус в диапазоне 200-299 // получаем тело ответа (см. про этот метод ниже) let json = await response.json(); >else
Во-вторых, для получения тела ответа нам нужно использовать дополнительный вызов метода.
Response предоставляет несколько методов, основанных на промисах, для доступа к телу ответа в различных форматах:
- response.text() – читает ответ и возвращает как обычный текст,
- response.json() – декодирует ответ в формате JSON,
- response.formData() – возвращает ответ как объект FormData (разберём его в следующей главе),
- response.blob() – возвращает объект как Blob (бинарные данные с типом),
- response.arrayBuffer() – возвращает ответ как ArrayBuffer (низкоуровневое представление бинарных данных),
- помимо этого, response.body – это объект ReadableStream, с помощью которого можно считывать тело запроса по частям. Мы рассмотрим и такой пример несколько позже.
Например, получим JSON-объект с последними коммитами из репозитория на GitHub:
let url = 'https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'; let response = await fetch(url); let commits = await response.json(); // читаем ответ в формате JSON alert(commits[0].author.login);
То же самое без await , с использованием промисов:
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits') .then(response => response.json()) .then(commits => alert(commits[0].author.login));
Для получения ответа в виде текста используем await response.text() вместо .json() :
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); let text = await response.text(); // прочитать тело ответа как текст alert(text.slice(0, 80) + '. ');
В качестве примера работы с бинарными данными, давайте запросим и выведем на экран логотип спецификации «fetch» (см. главу Blob, чтобы узнать про операции с Blob ):
let response = await fetch('/article/fetch/logo-fetch.svg'); let blob = await response.blob(); // скачиваем как Blob-объект // создаём
let img = document.createElement('img'); img.style = 'position:fixed;top:10px;left:10px;width:100px'; document.body.append(img); // выводим на экран img.src = URL.createObjectURL(blob); setTimeout(() => < // прячем через три секунды img.remove(); URL.revokeObjectURL(img.src); >, 3000);
Мы можем выбрать только один метод чтения ответа.
Если мы уже получили ответ с response.text() , тогда response.json() не сработает, так как данные уже были обработаны.
let text = await response.text(); // тело ответа обработано let parsed = await response.json(); // ошибка (данные уже были обработаны)
Заголовки ответа
Заголовки ответа хранятся в похожем на Map объекте response.headers .
Это не совсем Map , но мы можем использовать такие же методы, как с Map , чтобы получить заголовок по его имени или перебрать заголовки в цикле:
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); // получить один заголовок alert(response.headers.get('Content-Type')); // application/json; charset=utf-8 // перебрать все заголовки for (let [key, value] of response.headers) < alert(`$= $`); >
Заголовки запроса
Для установки заголовка запроса в fetch мы можем использовать опцию headers . Она содержит объект с исходящими заголовками, например:
let response = fetch(protectedUrl, < headers: < Authentication: 'secret' >>);
Есть список запрещённых 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-*
Эти заголовки обеспечивают достоверность данных и корректную работу протокола HTTP, поэтому они контролируются исключительно браузером.
POST-запросы
Для отправки POST -запроса или запроса с другим методом, нам необходимо использовать fetch параметры:
- method – HTTP метод, например POST ,
- body – тело запроса, одно из списка:
- строка (например, в формате JSON),
- объект FormData для отправки данных как form/multipart ,
- Blob / BufferSource для отправки бинарных данных,
- URLSearchParams для отправки данных в кодировке x-www-form-urlencoded , используется редко.
Чаще всего используется JSON.
Например, этот код отправляет объект user как JSON:
let user = < name: 'John', surname: 'Smith' >; let response = await fetch('/article/fetch/post/user', < method: 'POST', headers: < 'Content-Type': 'application/json;charset=utf-8' >, body: JSON.stringify(user) >); let result = await response.json(); alert(result.message);Заметим, что так как тело запроса body – строка, то заголовок Content-Type по умолчанию будет text/plain;charset=UTF-8 .
Но, так как мы посылаем JSON, то используем параметр headers для отправки вместо этого application/json , правильный Content-Type для JSON.
Отправка изображения
Мы можем отправить бинарные данные при помощи fetch , используя объекты Blob или BufferSource .
В этом примере есть элемент , на котором мы можем рисовать движением мыши. При нажатии на кнопку «Отправить» изображение отправляется на сервер:
Заметим, что здесь нам не нужно вручную устанавливать заголовок Content-Type , потому что объект Blob имеет встроенный тип ( image/png , заданный в toBlob ). При отправке объектов Blob он автоматически становится значением Content-Type .
Функция submit() может быть переписана без async/await , например, так:
function submit() < canvasElem.toBlob(function(blob) < fetch('/article/fetch/post/image', < method: 'POST', body: blob >) .then(response => response.json()) .then(result => alert(JSON.stringify(result, null, 2))) >, 'image/png'); >Итого
Типичный запрос с помощью fetch состоит из двух операторов await :
let response = await fetch(url, options); // завершается с заголовками ответа let result = await response.json(); // читать тело ответа в формате JSONfetch(url, options) .then(response => response.json()) .then(result => /* обрабатываем результат */)- response.status – HTTP-код ответа,
- response.ok – true , если статус ответа в диапазоне 200-299.
- response.headers – похожий на Map объект с HTTP-заголовками.
Методы для получения тела ответа:
- response.text() – возвращает ответ как обычный текст,
- response.json() – декодирует ответ в формате JSON,
- response.formData() – возвращает ответ как объект FormData (кодировка form/multipart, см. следующую главу),
- response.blob() – возвращает объект как Blob (бинарные данные с типом),
- response.arrayBuffer() – возвращает ответ как ArrayBuffer (низкоуровневые бинарные данные),
Опции fetch , которые мы изучили на данный момент:
- method – HTTP-метод,
- headers – объект с запрашиваемыми заголовками (не все заголовки разрешены),
- body – данные для отправки (тело запроса) в виде текста, FormData , BufferSource , Blob или UrlSearchParams .
В следующих главах мы рассмотрим больше параметров и вариантов использования fetch .
Задачи
Получите данные о пользователях GitHub
Создайте асинхронную функцию getUsers(names) , которая получает на вход массив логинов пользователей GitHub, запрашивает у GitHub информацию о них и возвращает массив объектов-пользователей.
Информация о пользователе GitHub с логином USERNAME доступна по ссылке: https://api.github.com/users/USERNAME .
В песочнице есть тестовый пример.
- На каждого пользователя должен приходиться один запрос fetch .
- Запросы не должны ожидать завершения друг друга. Надо, чтобы данные приходили как можно быстрее.
- Если какой-то запрос завершается ошибкой или оказалось, что данных о запрашиваемом пользователе нет, то функция должна возвращать null в массиве результатов.
Чтобы получить сведения о пользователе, нам нужно вызвать fetch(‘https://api.github.com/users/USERNAME’) .
Если ответ приходит cо статусом 200 , то вызываем метод .json() , чтобы прочитать JS-объект.
А если запрос завершается ошибкой или код статуса в ответе отличен от 200, то мы просто возвращаем null в массиве результатов.
async function getUsers(names) < let jobs = []; for(let name of names) < let job = fetch(`https://api.github.com/users/$`).then( successResponse => < if (successResponse.status != 200) < return null; >else < return successResponse.json(); >>, failResponse => < return null; >); jobs.push(job); > let results = await Promise.all(jobs); return results; >Пожалуйста, обратите внимание: вызов .then прикреплён к fetch , чтобы, когда ответ получен, сразу начинать считывание данных с помощью .json() , не дожидаясь завершения других запросов.
Если бы мы использовали await Promise.all(names.map(name => fetch(. ))) и вызывали бы .json() на результатах запросов, то пришлось бы ждать, пока завершатся все из них. Вызывая .json() сразу после каждого fetch , мы добились того, что считывание присланных по каждому запросу данных происходит независимо от других запросов.
Это пример того, как относительно низкоуровневое Promise API может быть полезным, даже если мы в основном используем async/await в коде.
Выполнение HTTP-запросов в Node.js с помощью node-fetch
Веб-приложению часто требуется взаимодействовать с веб-серверами для получения различных ресурсов. Возможно, вам потребуется извлечь данные или отправить данные на внешний веб-сервер или API.
Используя клиентский JavaScript, этого можно достичь с помощью API fetch и функции window.fetch() . В NodeJS несколько пакетов / библиотек могут достичь одного и того же результата. Один из них — node-fetch .
node-fetch — это небольшой модуль, который позволяет нам использовать функцию fetch() в NodeJS, с функциональностью, очень похожей на нативный window.fetch() , но с некоторыми отличиями:
node-fetch/v3-LIMITS.md at master · node-fetch/node-fetch · GitHub
A light-weight module that brings Fetch API to Node.js — node-fetch/node-fetch
https://github.com/node-fetch/node-fetch/blob/master/docs/v3-LIMITS.md
Начало работы с node-fetch
Чтобы использовать node-fetch в своем проекте, перейдите в каталог проекта и выполните:
npm install node-fetchЧтобы использовать модуль в коде, используйте:
var fetch = require('node-fetch');Как упоминалось ранее, функция fetch() в модуле node-fetch ведет себя очень похоже на window.fetch() . Его сигнатура:
fetch(url[, options])Параметр url просто прямой URL ресурса который мы хотим запросить. Это должен быть абсолютный URL, иначе функция выдаст ошибку. Необязательный параметр options используется, когда мы хотим использовать что-либо, кроме простого GET запроса, но мы поговорим об этом более подробно позже.
Функция возвращает объект Response , содержащий полезные функции и информацию об ответе HTTP, например:
- text() — возвращает тело ответа в виде строки
- json() — анализирует тело ответа на объект JSON и выдает ошибку, если тело не может быть проанализировано
- status и statusText — содержат информацию о коде статуса HTTP
- ok — равно true , если status это код состояния 2xx (успешный запрос)
- headers — объект, содержащий заголовки ответа, к определенному заголовку можно получить доступ с помощью функции get() .
Отправка запросов GET с использованием node-fetch
Есть два распространенных случая получения данных с веб-сервера. Возможно, вы захотите получить текст с веб-сервера, целую веб-страницу или данные с помощью REST API. Пакет node-fetch позволяет все это сделать.
Создайте каталог для вашего проекта и инициализируйте проект Node с настройками по умолчанию:
npm init -yЭто создаст файл package.json в каталоге. Затем установите node-fetch , как показано выше, и добавьте файл index.js .
Получение текста или веб-страниц
Сделаем простой GET запрос на главную страницу Google:
var fetch = require('node-fetch'); fetch('https://google.com') .then(res => res.text()) .then(text => console.log(text))В приведенном выше коде мы загружаем модуль node-fetch , а затем получаем домашнюю страницу Google. Единственный параметр, который мы добавили в функцию fetch() — это URL-адрес сервера, к которому мы отправляем HTTP-запрос. Поскольку node-fetch основан на обещаниях, мы объединяем в цепочку несколько функций .then() , чтобы помочь нам управлять ответом и данными из нашего запроса.
В этой строке мы ждем ответа от веб-сервера Google и конвертируем его в текстовый формат:
.then(res => res.text())Здесь ждем результата предыдущего преобразования и выводим его в консоль:
.then(text => console.log(text))Если мы запустим приведенный выше код из консоли:
node index.jsМы получим всю HTML-разметку домашней страницы Google, зарегистрированную в консоли:
Получение данных JSON из REST API
Другой распространенный вариант использования модуля node-fetch — получение данных с помощью REST API.
Мы получим поддельные данные пользователя из REST API JSONPlaceholder. Как и раньше, функция fetch() принимает URL-адрес сервера и ожидает ответа.
Давайте посмотрим, как это работает:
var fetch = require('node-fetch'); fetch('https://jsonplaceholder.typicode.com/users') .then(res => res.json()) .then(json => < console.log("First user in the array:") console.log(json[0]) console.log("Name of the first user in the array:") console.log(json[0].name) >)Тело ответа HTTP содержит данные в формате JSON, а именно массив, содержащий информацию о пользователях. Имея это в виду, мы использовали функцию .json() , и это позволило нам легко получить доступ к отдельным элементам и их полям.
Запуск этой программы даст нам:
First element in the array: < id: 1, name: 'Leanne Graham', username: 'Bret', email: 'Sincere@april.biz', address: < street: 'Kulas Light', suite: 'Apt. 556', city: 'Gwenborough', zipcode: '92998-3874', geo: < lat: '-37.3159', lng: '81.1496' >>, phone: '1-770-736-8031 x56442', website: 'hildegard.org', company: < name: 'Romaguera-Crona', catchPhrase: 'Multi-layered client-server neural-net', bs: 'harness real-time e-markets' >> Name of the first person in the array: Leanne GrahamМы также могли напечатать весь JSON, возвращенный res.json() .
Отправка запросов POST с помощью node-fetch
Мы также можем использовать эту функцию fetch() для публикации данных вместо их получения. Как мы упоминали ранее, что fetch() позволяет добавить дополнительный параметр для выполнения POST запросов к веб-серверу. Без этого необязательного параметра наш запрос по умолчанию является запросом GET .
Давайте добавим новый элемент в список задач JSONPlaceholder. Давайте добавим в этот список новый элемент для пользователя, у которого id равно 123 . Сначала нам нужно создать объект todo , а затем преобразовать его в JSON при добавлении в поле body :
var fetch = require('node-fetch'); var todo = < userId: 123, title: "loren impsum doloris", completed: false >fetch('https://jsonplaceholder.typicode.com/todos', < method: 'POST', body: JSON.stringify(todo), headers: < 'Content-Type': 'application/json' >>) .then(res => res.json()) .then(json => console.log(json))Процесс очень похож на отправку GET запроса. Мы вызвали функцию fetch() с соответствующим URL-адресом и установили необходимые параметры, используя необязательный параметр функции fetch() . Раньше мы преобразовывали наш объект в строку в формате JSON перед его отправкой на веб-сервер с помощью JSON.stringify() . Затем, как и при получении данных, мы ждали ответа, конвертировали его в JSON и выводили на консоль.
Запуск кода дает нам результат:
Обработка исключений и ошибок
Наши запросы иногда могут завершаться неудачно по разным причинам — из-за ошибки в функции fetch() , проблем с Интернетом, внутренних ошибок сервера и других. Нам нужен способ справиться с этими ситуациями или, по крайней мере, уметь видеть, что они произошли.
Мы можем обрабатывать исключения выполнения catch() , добавляя их в конец цепочки обещаний. Давайте добавим простую функцию catch() в нашу программу выше:
var fetch = require('node-fetch'); var todo = < userId: 123, title: "loren impsum doloris", completed: false >fetch('https://jsonplaceholder.typicode.com/todos', < method: 'POST', body: JSON.stringify(todo), headers: < 'Content-Type': 'application/json' >>) .then(res => res.json()) .then(json => console.log(json)) .catch(err => console.log(err))В идеале вы не должны просто игнорировать и распечатывать ошибки, а вместо этого иметь систему для их обработки.
Мы должны помнить, что если наш ответ имеет код состояния 3xx / 4xx / 5xx, запрос либо не выполнен, либо клиенту необходимо предпринять дополнительные шаги.
А именно, коды состояния HTTP 3xx указывают на то, что клиенту необходимо предпринять дополнительные шаги, коды 4xx указывают на недопустимый запрос, а коды 5xx указывают на ошибки сервера. Все эти коды состояния говорят нам, что наш запрос не был успешным с практической точки зрения.
catch() не будет регистрировать ни один из этих случаев, потому что связь с сервером прошла успешно, т.е. мы сделали запрос и получили ответ успешно. Это означает, что нам необходимо предпринять дополнительные шаги, чтобы убедиться, что мы охватили ситуацию, когда связь клиент-сервер была успешной, но мы не получили ни одного из успешных (2xx) кодов состояния HTTP.
Распространенный способ убедиться, что неудачные запросы вызывают ошибку, — это создать функцию, которая проверяет HTTP-статус ответа от сервера. В этой функции, если код состояния не указывает на успех, мы можем выдать ошибку и catch() поймать ее.
Мы можем использовать ранее упомянутое поле ok объекта Response , которое равно true , если код состояния равен 2xx.
Посмотрим, как это работает:
var fetch = require('node-fetch'); function checkResponseStatus(res) < if(res.ok)< return res >else < throw new Error(`The HTTP status of the reponse: $($)`) > > fetch('https://jsonplaceholder.typicode.com/MissingResource') .then(checkResponseStatus) .then(res => res.json()) .then(json => console.log(json)) .catch(err => console.log(err))Мы использовали функцию в начале цепочки обещаний (перед синтаксическим анализом тела ответа), чтобы узнать, столкнулись ли мы с проблемой. Вместо этого вы также можете выдать настраиваемую ошибку.
Опять же, у вас должна быть стратегия обработки подобных ошибок, а не просто вывод на консоль.
Если все прошло, как ожидалось, и код состояния указывает на успех, программа продолжит работу, как и раньше.
Вывод
Выполнение запросов к веб-серверам — обычная задача веб-разработки, и в этой статье мы увидели, как мы можем сделать это эффективно, используя библиотеку node-fetch , которая делает API-интерфейс fetch из браузера совместимым с NodeJS.
В дополнение к этому мы также рассмотрели, как обрабатывать ошибки, которые могут возникнуть с HTTP-запросами.
Хватит использовать Fetch API в JavaScript
Перед разработчиком нередко встает вопрос, какие использовать пакеты, инструменты и способы реализации задуманного. К примеру, каким способом стоит создавать HTTP-запросы? В данной статье я расскажу о библиотеке Axios и Fetch API, попутно продемонстрировав их отличия.
О чем вообще речь?
Когда требуется осуществлять информационный обмен с серверами через HTTP, мы отправляем запросы и получаем в ответ данные — этот процесс называется AJAX (асинхронный JavaScript и XML). Два самых известных ключевых слова, которые любой фронтенд-разработчик держит в арсенале для выполнения данной операции — это Axios и Fetch.
Axios
Axios — это библиотека JS, используемая для отправки HTTP-запросов из Node.js либо браузера. В документации указаны следующие ее возможности:
- Использование XMLHttpRequests из браузера;
- Выполнение HTTP запросов из Node.js;
- Поддержка Promise API;
- Перехват запроса и ответа;
- Преобразование данных запроса и ответа;
- Отмена запросов;
- Автоматическое преобразование для данных в формате JSON;
- Клиентская поддержка защиты от XSRF (межсайтовая подделка запросов).
Поскольку библиотека Axios не является нативной для JavaScript API, ее необходимо импортировать в проект. Поэтапную инструкцию установки можете найти здесь.
Fetch API
Fetch API предоставляет интерфейс для получения ресурсов из браузера. Согласно документации, Fetch дает общее определение объектов для запроса и ответа. Этот API также использует промисы и предоставляет глобальный метод fetch() . Данный метод требует в качестве необходимого аргумента только путь к нужному ресурсу. В ответ он возвращает промис, который разрешает ответ на этот запрос, независимо от того, успешен он был или нет.
Совместимость
Вот список ведущих браузеров, поддерживающих эти инструменты:
- Axios — Chrome, Firefox, Safari, Opera, Edge, IE.
- Fetch — Chrome, Firefox, Safari, Opera, Edge.
Можно смело сказать, что Axios отлично поддерживается в старых версиях браузеров, а также в IE, при том что Fetch API нет.
Безопасность
Axios является менее уязвимым, поскольку предоставляет защиту от XSRF на клиентской стороне. Как правило, такие атаки происходят в ситуациях, когда HTTP-запрос совершается между сайтами с целью выдать злоумышленника за истинного пользователя. Более подробно об этом виде атак можно прочесть в Википедии.
Синтаксис
При сравнении Axios и Fetch важно отметить, что они сильно отличаются в своем синтаксисе. Возьмем, к примеру, метод GET :
const url = 'https://jsonplaceholder.typicode.com/posts';// Axios
axios.get(url)
.then(response => console.log(response));// Fetch
fetch(url)
.then(response => response.json())
.then(data => console.log(data));Если вы с этими инструментами еще не сталкивались, то наверняка поинтересуетесь, почему в случае Fetch использован метод .json() . По факту то же самое происходит в Axios внутренне при выполнении автоматической сериализации в JSON после разрешения запроса.
Прерывание запросов и ответов
HTTP-перехватчики отлично подходят для проверки HTTP-запросов, передаваемых от клиента серверу. Они хороши для изменения запросов/ответов до момента их начала/получения. В Fetch нет способа перехвата HTTP-запросов, но есть возможность обойти это ограничение переопределением глобального метода fetch() и созданием его собственного перехватчика.
При этом в Axios уже есть встроенный перехватчик, который выполняется даже до промисов .then или .catch . Продемонстрирую:
axios.interceptors.request.use(
config => // Здесь можно выполнить нужные действия с данными запроса
console.log('A request has been sent!');
return config;
>, (error) => return Promise.reject(error);
>
);axios.interceptors.response.use(
config => // Здесь можно выполнить нужные действия с данными ответа
console.log('A response has been received!');
return config;
>, (error) => return Promise.reject(error);
>
);axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => console.log(response));Таймаут и прогресс запроса
Одно из действительно крутых преимуществ Axios в том, что Fetch не предоставляет среди своих настроек timeout , определяющий временное ограничение, по истечении которого запрос отменяется. Таймаут является возможной опцией, передаваемой в объект config и измеряется в миллисекундах. Важно помнить, что таймаут относится к ответу, а не соединению. Например:
axios.get(
'https://jsonplaceholder.typicode.com/posts',
timeout: 10
>
)
.then(response => console.log(response));Однако есть способ создать таймаут и с помощью Fetch API:
const controller = new AbortController();
const timeoutValue = 10000;
const timeout = setTimeout(() => controller.abort(), timeoutValue);
fetch('https://jsonplaceholder.typicode.com/posts', < signal: controller.signal >)
.then(response => response.json())
.then(data => console.log(data));Что касается прогресса, то и здесь есть очередной повод предпочесть Axios, поскольку он предоставляет более простой способ узнать о прогрессе запроса. Одной из опциональных настроек, передаваемой для этого в объект конфигурации, является OnUploadProgress .
Обработка ошибок
С помощью Axios обрабатывать ошибки проще, поскольку плохие ответы автоматически отвергаются в отличие от Fetch, где даже 404 или 500 ошибки все равно обрабатываются. В случае, когда сервер возвращает код “500 Internal Server Error”, Fetch обработает его аналогично коду “200 OK”. Для решения этой проблемы можно использовать флаг “ok”:
fetch('https://official-joke-api.appspot.com/jokes/ten/hehe')
.then(response => if (!response.ok) throw Error(response.statusText)
>
return response.json()
>)
.then(data => console.log(data)
>)
.catch(error => console.error(error))В случае же Axios простой пример обработки ошибки будет таким:
axios
.get('https://official-joke-api.appspot.com/jokes/ten/hehe')
.then(response => console.log("response", response)
>)
.catch(error => console.log(error))Заключение
Из рассмотренного материала очевидно, что Axios проще использовать, чем Fetch. Однако при этом также стоит учесть, что Axios является внешним пакетом, который потребуется включать в проект дополнительно. Fetch же изначально встроен в браузер, то есть дополнительного места не займет. Лично я предпочитаю использовать Axios, но это нельзя назвать правилом, так что окончательный выбор за вами. Делайте его с умом!
Fetch API
Давайте рассмотрим оставшуюся часть API, чтобы охватить все возможности.
На заметку:
Заметим: большинство этих возможностей используются редко. Вы можете пропустить эту главу и, несмотря на это, нормально использовать fetch .
Тем не менее, полезно знать, что вообще может fetch , чтобы, когда появится необходимость, вернуться и прочитать конкретные детали.
Нижеследующий список – это все возможные опции для fetch с соответствующими значениями по умолчанию (в комментариях указаны альтернативные значения):
let promise = fetch(url, < method: "GET", // POST, PUT, DELETE, etc. headers: < // значение этого заголовка обычно ставится автоматически, // в зависимости от тела запроса "Content-Type": "text/plain;charset=UTF-8" >, body: undefined, // string, FormData, Blob, BufferSource или URLSearchParams referrer: "about:client", // или "" для того, чтобы не послать заголовок Referer, // или URL с текущего источника referrerPolicy: "strict-origin-when-cross-origin", // no-referrer-when-downgrade, no-referrer, origin, same-origin. mode: "cors", // same-origin, no-cors credentials: "same-origin", // omit, include cache: "default", // no-store, reload, no-cache, force-cache или only-if-cached redirect: "follow", // manual, error integrity: "", // контрольная сумма, например "sha256-abcdef1234567890" keepalive: false, // true signal: undefined, // AbortController, чтобы прервать запрос window: window // null >);Довольно-таки внушительный список, не так ли?
В главе Fetch мы разобрали параметры method , headers и body .
Опция signal разъяснена в главе в Fetch: прерывание запроса.
Теперь давайте пройдёмся по оставшимся возможностям.
referrer, referrerPolicy
Данные опции определяют, как fetch устанавливает HTTP-заголовок Referer .
Обычно этот заголовок ставится автоматически и содержит URL-адрес страницы, с которой пришёл запрос. В большинстве случаев он совсем неважен, в некоторых случаях, с целью большей безопасности, имеет смысл убрать или укоротить его.
Опция referrer позволяет установить любой Referer в пределах текущего источника или же убрать его.
Чтобы не отправлять Referer , нужно указать значением пустую строку:
fetch('/page', < referrer: "" // не ставить заголовок Referer >);Для того, чтобы установить другой URL-адрес (должен быть с текущего источника):
fetch('/page', < // предположим, что мы находимся на странице https://javascript.info // мы можем установить любое значение Referer при условии, что оно принадлежит текущему источнику referrer: "https://javascript.info/anotherpage" >);Опция referrerPolicy устанавливает общие правила для Referer .
Выделяется 3 типа запросов:
- Запрос на тот же источник.
- Запрос на другой источник.
- Запрос с HTTPS to HTTP (с безопасного протокола на небезопасный).
В отличие от настройки referrer , которая позволяет задать точное значение Referer , настройка referrerPolicy сообщает браузеру общие правила, что делать для каждого типа запроса.
- «strict-origin-when-cross-origin» – значение по умолчанию: для «same-origin» отправлять полный Referer , для «cross-origin» отправлять только «origin» , если только это не HTTPS→HTTP запрос, тогда не отправлять ничего.
- «no-referrer-when-downgrade» – всегда отправлять полный Referer , за исключением случаев, когда мы отправляем запрос с HTTPS на HTTP (на менее безопасный протокол).
- «no-referrer» – никогда не отправлять Referer .
- «origin» – отправлять в Referer только текущий источник, а не полный URL-адрес страницы, например, посылать только http://site.com вместо http://site.com/path .
- «origin-when-cross-origin» – отправлять полный Referer для запросов в пределах текущего источника, но для запросов на другой источник отправлять только сам источник (как выше).
- «same-origin» – отправлять полный Referer для запросов в пределах текущего источника, а для запросов на другой источник не отправлять его вообще.
- «strict-origin» – отправлять только значение источника, не отправлять Referer для HTTPS→HTTP запросов.
- «unsafe-url» – всегда отправлять полный URL-адрес в Referer , даже при запросах HTTPS→HTTP .
Вот таблица со всеми комбинациями:
Значение На тот же источник На другой источник HTTPS→HTTP «no-referrer» — — — «no-referrer-when-downgrade» full full — «origin» origin origin origin «origin-when-cross-origin» full origin origin «same-origin» full — — «strict-origin» origin origin — «strict-origin-when-cross-origin» или «» (по умолчанию) full origin — «unsafe-url» full full full Допустим, у нас есть админка со структурой URL, которая не должна стать известной снаружи сайта.
Если мы отправляем запрос fetch , то по умолчанию он всегда отправляет заголовок Referer с полным URL-адресом нашей админки (исключение – это когда мы делаем запрос от HTTPS в HTTP, в таком случае Referer не будет отправляться).
Например, Referer: https://javascript.info/admin/secret/paths .
Если мы хотим, чтобы другие сайты получали только источник, но не URL-путь, это сделает такая настройка:
fetch('https://another.com/page', < // . referrerPolicy: "origin-when-cross-origin" // Referer: https://javascript.info >);Мы можем поставить её во все вызовы fetch , возможно, интегрировать в JavaScript-библиотеку нашего проекта, которая делает все запросы и внутри использует fetch .
Единственным отличием в поведении будет то, что для всех запросов на другой источник fetch будет посылать только источник в заголовке Referer (например, https://javascript.info , без пути). А для запросов на наш источник мы продолжим получать полный Referer (это может быть полезно для отладки).
Политика установки Referer (Referrer Policy) – не только для fetch
Политика установки Referer, описанная в спецификации Referrer Policy, существует не только для fetch , она более глобальная.
В частности, можно поставить политику по умолчанию для всей страницы, используя HTTP-заголовок Referrer-Policy , или на уровне ссылки .
mode
Опция mode – это защита от нечаянной отправки запроса на другой источник:
- «cors» – стоит по умолчанию, позволяет делать такие запросы так, как описано в Fetch: запросы на другие сайты,
- «same-origin» – запросы на другой источник запрещены,
- «no-cors» – разрешены только простые запросы на другой источник.
Эта опция может пригодиться, если URL-адрес для fetch приходит от третьей стороны, и нам нужен своего рода «глобальный выключатель» для запросов на другие источники.
credentials
Опция credentials указывает, должен ли fetch отправлять куки и авторизационные заголовки HTTP вместе с запросом.
- «same-origin» – стоит по умолчанию, не отправлять для запросов на другой источник,
- «include» – отправлять всегда, но при этом необходим заголовок Access-Control-Allow-Credentials в ответе от сервера, чтобы JavaScript получил доступ к ответу сервера, об этом говорилось в главе Fetch: запросы на другие сайты,
- «omit» – не отправлять ни при каких обстоятельствах, даже для запросов, сделанных в пределах текущего источника.
cache
По умолчанию fetch делает запросы, используя стандартное HTTP-кеширование. То есть, учитывается заголовки Expires , Cache-Control , отправляется If-Modified-Since и так далее. Так же, как и обычные HTTP-запросы.
Настройка cache позволяет игнорировать HTTP-кеш или же настроить его использование:
- «default» – fetch будет использовать стандартные правила и заголовки HTTP кеширования,
- «no-store» – полностью игнорировать HTTP-кеш, этот режим становится режимом по умолчанию, если присутствуют такие заголовки как If-Modified-Since , If-None-Match , If-Unmodified-Since , If-Match , или If-Range ,
- «reload» – не брать результат из HTTP-кеша (даже при его присутствии), но сохранить ответ в кеше (если это дозволено заголовками ответа);
- «no-cache» – в случае, если существует кешированный ответ – создать условный запрос, в противном же случае – обычный запрос. Сохранить ответ в HTTP-кеше,
- «force-cache» – использовать ответ из HTTP-кеша, даже если он устаревший. Если же ответ в HTTP-кеше отсутствует, сделать обычный HTTP-запрос, действовать как обычно,
- «only-if-cached» – использовать ответ из HTTP-кеша, даже если он устаревший. Если же ответ в HTTP-кеше отсутствует, то выдаётся ошибка. Это работает, только когда mode установлен в «same-origin» .
redirect
Обычно fetch прозрачно следует HTTP-редиректам, таким как 301, 302 и так далее.
Это можно поменять при помощи опции redirect :
- «follow» – стоит по умолчанию, следовать HTTP-редиректам,
- «error» – ошибка в случае HTTP-редиректа,
- «manual» – не следовать HTTP-редиректу, но установить адрес редиректа в response.url , а response.redirected будет иметь значение true , чтобы мы могли сделать перенаправление на новый адрес вручную.
integrity
Опция integrity позволяет проверить, соответствует ли ответ известной заранее контрольной сумме.
Как описано в спецификации, поддерживаемыми хеш-функциями являются SHA-256, SHA-384 и SHA-512. В зависимости от браузера, могут быть и другие.
Например, мы скачиваем файл, и мы точно знаем, что его контрольная сумма по алгоритму SHA-256 равна «abcdef» (разумеется, настоящая контрольная сумма будет длиннее).
Мы можем добавить это в настройку integrity вот так:
fetch('http://site.com/file', < integrity: 'sha256-abcdef' >);Затем fetch самостоятельно вычислит SHA-256 и сравнит его с нашей строкой. В случае несоответствия будет ошибка.
keepalive
Опция keepalive указывает на то, что запрос может «пережить» страницу, которая его отправила.
Например, мы собираем статистические данные о том, как посетитель ведёт себя на нашей странице (на что он кликает, части страницы, которые он просматривает), для анализа и улучшения интерфейса.
Когда посетитель покидает нашу страницу – мы хотим сохранить собранные данные на нашем сервере.
Для этого мы можем использовать событие window.onunload :
window.onunload = function() < fetch('/analytics', < method: 'POST', body: "statistics", keepalive: true >); >;Обычно, когда документ выгружается, все связанные с ним сетевые запросы прерываются. Но настройка keepalive указывает браузеру выполнять запрос в фоновом режиме даже после того, как пользователь покидает страницу. Поэтому эта опция обязательна, чтобы такой запрос удался.
У неё есть ряд ограничений:
- Мы не можем посылать мегабайты: лимит тела для запроса с keepalive – 64кб.
- Если мы собираем больше данных, можем отправлять их регулярно, «пакетами», тогда на момент последнего запроса в onunload их останется немного.
- Этот лимит распространяется на все запросы с keepalive . То есть, мы не можем его обойти, послав 100 запросов одновременно – каждый по 64Кбайт.
- Обычно сервер посылает пустой ответ на такие запросы, так что это не является проблемой.