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
- Un ejemplo básico de Fetch
- Fetch del lado del cliente vs Fetch del lado del servidor
- Solicitudes personalizadas de Fetch
- Manejo de encabezados HTTP
- Resolución y rechazo de Promises en Fetch
- Análisis de respuestas de Fetch
- Anulación de solicitudes de Fetch
- Fetches efectivos
- Resumen
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 enhttps://domainA.com/
, comohttps://domainA.com/api/
ohttps://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. -
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, configurardefault-src 'self'
evita quefetch()
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 defetch()
- 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.
Leave a Reply