Cómo usar la API Fetch en Node.js, Deno y Bun — CodesCode

¡Aprende a usar la API Fetch - una alternativa más sencilla, fácil y basada en promesas al XMLHttpRequest - con Node.js, Deno y Bun!

En este artículo, veremos cómo utilizar la Fetch API con Node.js, Deno y Bun.

Fetch API vs XMLHttpRequest

Realizar una solicitud HTTP para obtener datos es una actividad fundamental en las aplicaciones web. Es posible que hayas realizado tales llamadas en el navegador, pero la Fetch API es compatible de forma nativa con Node.js, Deno y Bun.

En un navegador, es posible que solicites información a un servidor para mostrarla sin tener que refrescar toda la pantalla. Esto se conoce comúnmente como una solicitud Ajax o una aplicación de página única (SPA). Entre 1999 y 2015, XMLHttpRequest fue la única opción, y sigue siendo así si deseas mostrar el progreso de la carga de archivos. XMLHttpRequest es una API basada en callbacks bastante engorrosa, pero permite un control detallado y, a pesar de su nombre, puede manejar respuestas en formatos distintos a XML, como texto, binario, JSON y HTML.

Los navegadores implementaron la Fetch API a partir de 2015. Se trata de una alternativa más simple, fácil y consistente basada en Promises a XMLHttpRequest.

Tu código del lado del servidor también puede necesitar realizar solicitudes HTTP, normalmente para llamar a APIs en otros servidores. A partir de su primera versión, tanto Deno como Bun replicaron útilmente la Fetch API del navegador para que el código similar pueda ejecutarse tanto en el cliente como en el servidor. Node.js requería un módulo de terceros como node-fetch o axios hasta febrero de 2022, cuando la versión 18 agregó la Fetch API estándar. Aunque aún se considera experimental, ahora puedes usar fetch() en todas partes con un código idéntico en la mayoría de los casos.

Un ejemplo básico de Fetch

Este sencillo ejemplo obtiene datos de respuesta desde una URI:

const response = await fetch('https://example.com/data.json');

La llamada a fetch() devuelve una Promise que se resuelve con un objeto de respuesta proporcionando información sobre el resultado. Puedes analizar el cuerpo de la respuesta HTTP en un objeto JavaScript utilizando el método basado en Promises .json():

const data = await response.json();// haz algo emocionante con el objeto de datos// ...

Fetch del lado del cliente vs Fetch del lado del servidor

La API puede ser idéntica en todas las plataformas, pero los navegadores imponen restricciones al realizar solicitudes de Fetch del lado del cliente:

  • Cross-origin resource sharing (CORS)

    JavaScript del lado del cliente solo puede comunicarse con puntos finales de API dentro de su propio dominio. Un script cargado desde https://domainA.com/js/main.js puede llamar a cualquier servicio en https://domainA.com/, como https://domainA.com/api/ o https://domainA.com/data/.

    Es imposible llamar a un servicio en https://domainB.com/ a menos que ese servidor permita el acceso configurando un encabezado HTTP Access-Control-Allow-Origin.

  • Content Security Policy (CSP)

    Tus sitios/aplicaciones web pueden establecer un encabezado HTTP Content-Security-Policy o una etiqueta meta para controlar los activos permitidos en una página. Esto puede evitar la inyección accidental o maliciosa de scripts, iframes, fuentes, imágenes, videos, etc. Por ejemplo, configurar default-src 'self' evita que fetch() solicite datos fuera de su propio dominio (XMLHttpRequest, WebSocket, eventos enviados por el servidor y balizas también están restringidos).

Llamadas de la API Fetch del lado del servidor en Node.js, Deno y Bun tienen menos restricciones y puedes solicitar datos desde cualquier servidor. Dicho esto, las APIs de terceros pueden:

  • requerir algún tipo de autenticación o autorización utilizando claves o OAuth
  • tener límites máximos de solicitud, como no más de una llamada por minuto, o
  • realizar un cargo comercial por el acceso

Puedes utilizar llamadas de fetch() del lado del servidor para hacer peticiones del lado del cliente y evitar problemas de CORS y CSP. Dicho esto, ¡recuerda ser un ciudadano consciente de la web y no bombardear los servicios con miles de solicitudes que podrían derribarlos!

Peticiones personalizadas con Fetch

El ejemplo anterior solicita datos desde la URI https://example.com/data.json. Por debajo, JavaScript crea un objeto Request, que representa todos los detalles de esa solicitud, como el método, encabezados, cuerpo y más.

fetch() acepta dos argumentos:

  • el recurso – una cadena o objeto URL, y
  • un parámetro opcional options con más configuraciones de la solicitud

Por ejemplo:

const response = await fetch('https://example.com/data.json', {   method: 'GET',   credentials: 'omit',   redirect: 'error',   priority: 'high'});

El objeto options puede establecer las siguientes propiedades en Node.js o en código del lado del cliente:

propiedad valores
method GET (el valor predeterminado), POST, PUT, PATCH, DELETE o HEAD
headers una cadena o objeto Headers
body puede ser una cadena, JSON, blob, etc.
mode same-origin, no-cors o cors
credentials omit, same-origin o include cookies y encabezados de autenticación HTTP
redirect control de redireccionamiento follow, error o manual
referrer la URL de referencia
integrity hash de integridad del subrecurso
signal un objeto AbortSignal para cancelar la solicitud

Opcionalmente, puedes crear un objeto Request y pasarlo a fetch(). Esto puede ser práctico si puedes definir puntos de conexión de la API de antemano o si quieres enviar una serie de solicitudes similares:

const request = new Request('https://example.com/api/', {  method: 'POST',  body: '{"a": 1, "b": 2, "c": 3}',  credentials: 'omit'});console.log(`solicitando ${ request.url }`);const response = await fetch(request);

Manipulación de encabezados HTTP

Puedes manipular y examinar los encabezados HTTP en la solicitud y respuesta utilizando un objeto Headers. La API te será familiar si has utilizado Mapas de JavaScript:

// establecer encabezados iniciales const headers = new Headers({  'Content-Type': 'text/plain',});// agregar encabezadoheaders.append('Authorization', 'Basic abc123');// agregar/cambiar encabezadoheaders.set('Content-Type', 'application/json');// obtener un encabezadoconst type = headers.get('Content-Type');// ¿tiene un encabezado?if (headers.has('Authorization')) {   // eliminar un encabezado   headers.delete('Authorization');}// iterar a través de todos los encabezadosheaders.forEach((value, name) => {  console.log(`${ name }: ${ value }`);});// usar en fetch()const response = await fetch('https://example.com/data.json', {   method: 'GET',   headers});// response.headers también devuelve un objeto Headersresponse.headers.forEach((value, name) => {  console.log(`${ name }: ${ value }`);});

Fetch Promise Resolve and Reject

Puedes suponer que una promesa de fetch() se rechazará cuando un endpoint devuelva un error de servidor 404 Not Found u otro similar. ¡Pero no lo hace! La promesa se resolverá, porque esa llamada fue exitosa, aunque el resultado no sea el esperado.

Una promesa de fetch() solo se rechaza cuando:

  • haces una solicitud no válida, como fetch('httttps://!invalid\URL/');
  • cancelas la solicitud de fetch(), o
  • hay un error de red, como una falla en la conexión

Analizando las respuestas de Fetch

Las llamadas exitosas de fetch() devuelven un objeto Response que contiene información sobre el estado y los datos devueltos. Las propiedades son:

propiedad descripción
ok true si la respuesta fue exitosa
status el código de estado HTTP, como 200 para éxito
statusText el texto de estado de HTTP, como OK para un código 200
url la URL
redirected true si la solicitud fue redirigida
type el tipo de respuesta: basic, cors, error, opaque, o opaqueredirect
headers el objeto de cabeceras de respuesta
body un ReadableStream del contenido del cuerpo (o null)
bodyUsed true si el cuerpo ha sido leído

Los siguientes métodos del objeto Response también devuelven una promesa, por lo que debes usar bloques await o .then:

método descripción
text() devuelve el cuerpo como una cadena de texto
json() interpreta el cuerpo como un objeto JavaScript
arrayBuffer() devuelve el cuerpo como un ArrayBuffer
blob() devuelve el cuerpo como un Blob
formData() devuelve el cuerpo como un objeto FormData de pares clave/valor
clone() clona la respuesta, normalmente para poder analizar el cuerpo de diferentes formas
// ejemplo de respuestaconst response = await fetch('https://example.com/data.json');// ¿devolvió respuesta JSON?if ( response.ok && response.headers.get('Content-Type') === 'application/json') {   // parse JSON   const obj = await response.json();}

Abortando las Solicitudes de Fetch

Node.js no cancelará una solicitud de fetch(); ¡puede continuar para siempre! Los navegadores también pueden esperar entre uno y cinco minutos. Deberías abortar fetch() en circunstancias normales donde esperas una respuesta razonablemente rápida.

El siguiente ejemplo utiliza un objeto AbortController, que pasa una propiedad de signal al segundo parámetro de fetch(). Un temporizador ejecuta el método .abort() si fetch no se completa en cinco segundos:

// crear AbortController para cancelar después de 5 segundos
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) {
  // tiempo de espera o error de red
  console.log(err);
}

Node.js, Deno, Bun y la mayoría de los navegadores lanzados desde mediados de 2022 también admiten AbortSignal. Esto ofrece un método más sencillo timeout() para que no tengas que administrar tus propios temporizadores:

try {
  // cancelar después de 5 segundos
  const response = await fetch('https://example.com/slowrequest/', { signal: AbortSignal.timeout(5000) });
  console.log(response.ok);
} catch (err) {
  // tiempo de espera o error de red
  console.log(err);
}

Fetches Efectivos

Al igual que cualquier operación asíncrona basada en promesas, solo debes hacer llamadas fetch() en serie cuando la entrada de una llamada depende de la salida de la anterior. El código siguiente no funciona tan bien como podría porque cada llamada a la API debe esperar a que se resuelva o se rechace la anterior. Si cada respuesta tarda un segundo, tardará un total de tres segundos en completarse:

// ineficiente
const response1 = await fetch('https://example1.com/api/');
const response2 = await fetch('https://example2.com/api/');
const response3 = await fetch('https://example3.com/api/');

El método Promise.allSettled() ejecuta promesas en paralelo y se cumple cuando todas se han resuelto o rechazado. Este código se completa a la velocidad de la respuesta más lenta. Será tres veces más rápido:

const data = await Promise.allSettled([
  'https://example1.com/api/',
  'https://example2.com/api/',
  'https://example3.com/api/'
].map(url => fetch(url)));

data devuelve un array de objetos donde:

  • cada uno tiene una propiedad de status como cadena de texto de "fullfilled" o "rejected"
  • si se resolvió, una propiedad de value devuelve la respuesta de fetch()
  • si se rechazó, una propiedad de reason devuelve el error

Resumen

A menos que estés utilizando una versión heredada de Node.js (17 o anterior), la API Fetch está disponible en JavaScript tanto en el servidor como en el cliente. Es flexible, fácil de usar y consistente en todos los entornos de ejecución. Solo será necesario un módulo de terceros si necesitas funcionalidades más avanzadas como caché, reintentos o manejo de archivos.

Comparte este artículo


Leave a Reply

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