Как использовать Fetch API в Node.js, Deno и Bun — CodesCode
Узнайте, как использовать Fetch API - более простой и удобный альтернативный метод XMLHttpRequest - с помощью Node.js, Deno и Bun.
В этой статье мы рассмотрим, как использовать Fetch API с Node.js, Deno и Bun.
- Fetch API vs XMLHttpRequest
- Пример простого Fetch
- Fetch на стороне клиента против Fetch на стороне сервера
- Настройка Fetch-запросов
- Работа с HTTP-заголовками
- Разрешение и отклонение Promise Fetch
- Анализ ответов Fetch
- Прерывание Fetch-запросов
- Эффективные Fetch-запросы
- Выводы
Fetch API против XMLHttpRequest
Получение данных посредством HTTP-запроса является основной задачей веб-приложений. Вы, возможно, делали такие вызовы в браузере, но Fetch API поддерживается нативно в Node.js, Deno и Bun.
В браузере вы можете запрашивать информацию с сервера, чтобы отобразить ее без полной перезагрузки экрана. Это обычно называется Ajax-запрос или одностраничное приложение (SPA). В период с 1999 по 2015 год XMLHttpRequest был единственным вариантом – и остается таким, если вам нужно отображать прогресс загрузки файлов. XMLHttpRequest является достаточно громоздким API на основе обратных вызовов, но он позволяет тонко настроить запросы и обрабатывать ответы в форматах, отличных от XML, таких как текст, двоичный код, JSON и HTML.
Браузеры внедрили Fetch API с 2015 года. Это более простая, удобная и последовательная альтернатива XMLHttpRequest, основанная на промисах.
Ваш код на стороне сервера также может обратиться к HTTP-запросам – обычно для вызова API на других серверах. С момента своего первого релиза Deno и Bun успешно повторили Fetch API браузера, так что аналогичный код может выполняться на клиенте и сервере. Node.js требовал сторонний модуль, такой как node-fetch или axios до февраля 2022 года, когда версия 18 добавила стандартный Fetch API. Он все еще считается экспериментальным, но в большинстве случаев теперь можно использовать fetch()
везде с идентичным кодом.
Пример простого Fetch
Этот простой пример получает данные отклика по URI:
const response = await fetch('https://example.com/data.json');
Функция fetch()
возвращает промис, который разрешается с объектом Response object, предоставляющим информацию о результате. Вы можете разобрать тело HTTP-ответа в объект JavaScript, используя метод .json()
на основе промисов:
const data = await response.json();// сделайте что-то захватывающее с объектом данных// ...
Fetch на стороне клиента против Fetch на стороне сервера
API может быть идентичным на всех платформах, но браузеры налагают ограничения при выполнении клиентских запросов fetch()
:
-
Cross-origin resource sharing (CORS)
JavaScript на клиентской стороне может общаться только с конечными точками API в своем собственном домене. Сценарий, загруженный из
https://domainA.com/js/main.js
, может вызывать любой сервис наhttps://domainA.com/
, такие какhttps://domainA.com/api/
илиhttps://domainA.com/data/
.Вы не сможете обратиться к сервису на
https://domainB.com/
– если только сервер не разрешит доступ, установив заголовок HTTP Access-Control-Allow-Origin. -
Ваши веб-сайты/приложения могут установить заголовок HTTP
Content-Security-Policy
или мета-тег, чтобы контролировать разрешенные ресурсы на странице. Это может предотвратить случайное или злонамеренное внедрение скриптов, iframe, шрифтов, изображений, видео и т. д. Например, установкаdefault-src 'self'
останавливаетfetch()
запрос данных вне своего собственного домена (XMLHttpRequest, WebSocket, серверные события и действия “маяков” также ограничены).
Вызовы Fetch API на стороне сервера в Node.js, Deno и Bun имеют меньше ограничений, и вы можете запрашивать данные с любого сервера. Однако сторонние API могут:
- требовать некоторой аутентификации или авторизации с использованием ключей или OAuth
- иметь максимальные пороги запросов, например, не более одного вызова в минуту, или
- взимать коммерческую плату за доступ
Вы можете использовать вызовы fetch()
на стороне сервера для проксирования запросов на стороне клиента, чтобы избежать проблем с CORS и CSP. Тем не менее, помните быть добросовестным пользователем веба и не обстреливать сервисы тысячами запросов, которые могут их перегрузить!
Пользовательские запросы Fetch
Приведенный выше пример запрашивает данные по адресу https://example.com/data.json
. Под капотом JavaScript создает объект Request, который представляет полные детали этого запроса, такие как метод, заголовки, тело и другое.
fetch()
принимает два аргумента:
- ресурс – строка или URL-объект,
- необязательный параметр options с дополнительными настройками запроса
Например:
const response = await fetch('https://example.com/data.json', { method: 'GET', credentials: 'omit', redirect: 'error', priority: 'high'});
Объект options может устанавливать следующие свойства как в Node.js, так и в коде на стороне клиента:
свойство | значения |
---|---|
method |
GET (по умолчанию), POST , PUT , PATCH , DELETE или HEAD |
headers |
строка или объект Headers |
body |
может быть строкой, JSON, blob и т. д. |
mode |
same-origin , no-cors или cors |
credentials |
omit , same-origin или include (cookies и заголовки аутентификации HTTP) |
redirect |
follow , error или manual обработка перенаправлений |
referrer |
URL-адрес, с которого был осуществлен переход |
integrity |
хеш целостности подресурса |
signal |
объект AbortSignal для отмены запроса |
По желанию, вы можете создать объект Request и передать его в fetch()
. Это может быть практично, если вы можете определить точки доступа API заранее или хотите отправить серию похожих запросов:
const request = new Request('https://example.com/api/', { method: 'POST', body: '{"a": 1, "b": 2, "c": 3}', credentials: 'omit'});console.log(`запрос ${ request.url }`);const response = await fetch(request);
Обработка HTTP-заголовков
Вы можете изменять и проверять HTTP-заголовки в запросе и ответе, используя объект Headers. API будет знаком, если вы уже использовали JavaScript Maps:
// устанавливаем исходные заголовкиconst headers = new Headers({ 'Content-Type': 'text/plain',});// добавляем заголовокheaders.append('Authorization', 'Basic abc123');// добавляем/изменяем заголовокheaders.set('Content-Type', 'application/json');// получаем заголовокconst type = headers.get('Content-Type');// есть ли заголовок?if (headers.has('Authorization')) { // удаляем заголовок headers.delete('Authorization');}// перебираем все заголовкиheaders.forEach((value, name) => { console.log(`${ name }: ${ value }`);});// используем в fetch()const response = await fetch('https://example.com/data.json', { method: 'GET', headers});// response.headers также возвращает объект Headersresponse.headers.forEach((value, name) => { console.log(`${ name }: ${ value }`);});
Fetch Promise Resolve and Reject
Вы, возможно, думаете, что промис fetch()
будет отклонен, когда сервер вернет ошибку 404 Not Found
или другую ошибку сервера. Нет! Промис будет разрешен, потому что вызов был успешным, даже если результат не соответствует вашим ожиданиям.
Промис fetch()
будет отклонен только в следующих случаях:
- вы делаете недопустимый запрос — например,
fetch('httttps://!invalid\URL/');
- вы прерываете запрос
fetch()
, - происходит сетевая ошибка, например, сбой соединения.
Анализ ответов Fetch
Успешные вызовы fetch()
возвращают объект Response, содержащий информацию о состоянии и возвращенных данных. Свойства объекта:
свойство | описание |
---|---|
ok |
true , если ответ был успешным |
status |
код состояния HTTP, например, 200 для успешного состояния |
statusText |
текстовое описание состояния HTTP, например, OK для кода 200 |
url |
URL |
redirected |
true , если запрос был перенаправлен |
type |
тип ответа: basic , cors , error , opaque или opaqueredirect |
headers |
объект Headers ответа |
body |
ReadableStream содержимого тела (или null) |
bodyUsed |
true , если тело было прочитано |
Все следующие методы объекта Response возвращают промис, поэтому вы должны использовать блоки await
или .then
:
метод | описание |
---|---|
text() |
возвращает тело в виде строки |
json() |
преобразует тело в JavaScript-объект |
arrayBuffer() |
возвращает тело в виде ArrayBuffer |
blob() |
возвращает тело в виде Blob |
formData() |
возвращает тело в виде объекта FormData с ключами и значениями |
clone() |
клонирует ответ, обычно чтобы разобрать тело разными способами |
// пример ответаconst response = await fetch('https://example.com/data.json');// вернулся ли ответ в формате JSON?if (response.ok && response.headers.get('Content-Type') === 'application/json') { // разбор JSON const obj = await response.json();}
Отмена запросов Fetch
Node.js не отменяет запрос fetch()
, так как он может выполняться бесконечно долго! Браузеры также могут ожидать от одной до пяти минут. Вы должны отменять fetch()
в обычных ситуациях, когда ожидается относительно быстрый ответ.
В следующем примере используется объект AbortController, который передает свойство signal
во второй параметр fetch()
. Если fetch не завершается в течение пяти секунд, то выполняется метод .abort()
:
// создаем AbortController для отмены запроса через 5 секунд
const controller = new AbortController(),
signal = controller.signal,
timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://example.com/slowrequest/', { signal });
clearTimeout(timeout);
console.log(response.ok);
} catch (err) {
// ошибка времени ожидания или сетевая ошибка
console.log(err);
}
Node.js, Deno, Bun и большинство браузеров, выпущенных с середины 2022 года, также поддерживают AbortSignal. Это предлагает более простой метод timeout(), поэтому вам не нужно самостоятельно управлять таймерами:
try {
// отменить запрос через 5 секунд
const response = await fetch('https://example.com/slowrequest/', {
signal: AbortSignal.timeout(5000),
});
console.log(response.ok);
} catch (err) {
// ошибка времени ожидания или сетевая ошибка
console.log(err);
}
Эффективные запросы с помощью Fetch
Как и любая асинхронная операция на основе промисов, вы должны делать вызовы fetch()
последовательно только в том случае, если ввод каждого вызова зависит от вывода предыдущего. Следующий код работает менее эффективно, чем может, потому что каждый вызов API должен ждать завершения или отклонения предыдущего вызова. Если каждый ответ занимает одну секунду, общее время выполнения будет три секунды:
// неэффективный код
const response1 = await fetch('https://example1.com/api/');
const response2 = await fetch('https://example2.com/api/');
const response3 = await fetch('https://example3.com/api/');
Метод Promise.allSettled() выполняет промисы параллельно и досягает выполнения, когда все они завершаются или отклоняются. Этот код завершается со скоростью самого медленного ответа. Он будет в три раза быстрее:
const data = await Promise.allSettled(
[
'https://example1.com/api/',
'https://example2.com/api/',
'https://example3.com/api/'
].map(url => fetch(url))
);
data
возвращает массив объектов, где:
- каждый объект имеет свойство
status
со строковым значением"fulfilled"
или"rejected"
- если промис выполнился успешно, свойство
value
возвращает ответ отfetch()
- если промис был отклонен, свойство
reason
возвращает ошибку
Вывод
За исключением использования устаревшей версии Node.js (17 и ниже), API Fetch доступен в JavaScript как на сервере, так и на клиенте. Он гибкий, легко использовать и согласован во всех средах выполнения. Сторонний модуль должен быть необходим только в случае, если вам требуется более продвинутая функциональность, такая как кэширование, повторные попытки или работа с файлами.
Leave a Reply