Как использовать Fetch API в Node.js, Deno и Bun — CodesCode

Узнайте, как использовать Fetch API - более простой и удобный альтернативный метод XMLHttpRequest - с помощью Node.js, Deno и Bun.

В этой статье мы рассмотрим, как использовать Fetch API с Node.js, Deno и Bun.

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.

  • Content Security Policy (CSP)

    Ваши веб-сайты/приложения могут установить заголовок 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

Your email address will not be published. Required fields are marked *