Fetch API, la guia completa para hacer llamadas HTTP en javascript y olvidarte de axios

Javascript abr. 14, 2020

Con la llegada del es6 para javascript, surgieron nuevas caracter√≠sticas que facilitaron el uso del lenguaje en tareas que anteriormente hicieron a muchos desarrolladores orientarse por utilizar JQuery, como las peticiones HTTP, sin embargo ha pasado el tiempo y las webapps modernas necesitan nuevas herramientas potentes, y esa es la raz√≥n para mirar hacia Fetch API, tanto as√≠ probablemente no necesites m√°s de Jquery ni de axios ni de ninguna otra herramienta m√°s al terminar de leer esta entrada, as√≠ que acom√≥date bien y acomp√°√Īame en las siguientes l√≠neas donde detallar√© todo este tema.

Tiempos memoriales de XMLHttpRequest

Recuerdo que hace algunos a√Īos, solo tenias dos opciones para hacer peticiones HTTP, eran XMLHttpRequest puro y duro o solicitudes AJAX de JQuery para abreviar el mismo proceso, pero es m√°s, incluso hab√≠an y hay a√ļn desarrolladores que realizan el siguiente proceso: primero aprenden a realizar peticiones utilizando XHR y luego debido a la simplicidad dec√≠an: "para ya no demorar mejor JQuery", y sabes que...en parte tienen raz√≥n, porque en muchos casos es necesario realizar un proyecto de forma r√°pida, o para un prototipo que no necesita mayor complicaci√≥n, pero en otros casos donde la performance y peso de la aplicaci√≥n es importante pensando a largo plazo, es mejor evitar utilizar todo el paquete completo que trae JQuery.

Pero como ning√ļn mal dura 100 a√Īos, ahora tenemos disponible a Fetch API, una caracter√≠stica que viene a aportar cosas interesantes, continuemos...

Fetch API

B√°sicamente es una API de javascript que a trav√©s de su interfaz te permite realizar peticiones HTTP para obtener recursos de la red, globalmente expone un objeto del lado del navegador llamado fetch. D√©jame contarte que desde el inicio, cuando reci√©n sali√≥ en forma experimental √©sta caracter√≠stica se not√≥ flexible, esto empez√≥ a llamar la atenci√≥n de gran cantidad de desarrolladores, y luego con el paso del tiempo, con multiples cambios realizados a lo largo de los a√Īos se ha vuelto m√°s robusta.

¬ŅPor qu√© elegir Fetch API sobre axios o request?

Buenas pregunta, sin embargo cuando miramos las comparaciones puedes tener datos sumamente reveladores.

Comparación fetch vs axios vs request

Interceptar solicitudes y respuestas, trasformar esos mismos datos y la cancelación de solicitudes son las 3 primeras características que personalmente enamoran, por otro lado, poder ver el progreso de tu solicitud y streaming de datos en fetch son cosas que simplemente hacen explotar tu imaginación.

Si bien la mayor√≠a de las caracter√≠sticas est√°n disponibles tambi√©n en sus competidores directos (axios y request), la gran ventaja de fetch es que no necesitas agregar ninguna dependencia de terceros para poder hacer lo mismo...entonces...¬Ņt√ļ que eliges?... te dejo con esa pregunta mente, ahora vamos a meter las manos en la masa.

Flujo b√°sico de fetch

Fetch tiene un proceso de solicitud super sencillo de entender:

  1. Realizas la solicitud.
  2. Devuelve una promesa, que resuelve un objeto Response.
  3. El objeto response es le√≠do a trav√©s de funciones seg√ļn el tipo de dato (json, blob, text, etc.).

Sintaxis est√°ndar de solicitud

La sintaxis b√°sica para realizar peticiones es la siguiente:

fetch(url) // 1
.then(response => response.json()) // 2
.then(console.log) // 3
.catch(console.log('Algo salió mal.'));
Sintaxis solicitud fetch

La ventaja de utilizar promesas es que estas nos permiten encadenarlas, de tal forma que el resultado de una promesa es pasado como parámetro hacia la siguiente promesa, a menos que se produzca un error y se pase directamente hacia una función catch(), con eso en mente tenemos las siguientes instrucciones:

  1. Realiza la solicitud a una determinada URL.
  2. Resuelve la promesa, al obtener respuesta la pasa a un determinado formato utilizando la función correspondiente, en este caso JSON.
  3. Lee el objeto data y lo imprime con un console.log().
  4. Si hay un error es atrapado por la función catch.

Ejemplo b√°sico

Bien, ahora materializamos el ejemplo realizando una solicitud super b√°sica hacia la API de jsonplaceholder.

fetch('https://jsonplaceholder.typicode.com/todos/1') // 1
.then(response => response.json()) // 2
.then(console.log); // 3

// {"userId":1,"id":1,"title":"delectus aut autem","completed":false}
Ejemplo b√°sico

Detallamos el proceso de la siguiente manera:

  1. Realizamos la solicitud a la API de jsonplaceholder (esa función devuelve una promesa).
  2. La promesa es resuelta gracias al then(), que tiene una función espera un objeto Response, y ese objeto nos permite usar la función json() para resolver la data que viene en ese formato (esa función devuelve una promesa - otra vez).
  3. La segunda promesa es resuelta con el segundo then(), y es pasada al console.log (que también es una función y espera N parámetros) para imprimir el resultado.

Realizando una solicitud / petición

Aqu√≠ es donde inicia la magia y fetch provee una forma de configurar f√°cilmente las opciones que necesitas asignar para realizar tus peticiones, muchos de estos son opcionales seg√ļn el tipo de solicitud, veamos algunas:

  • method: M√©todo de la solicitud, por ejemplo GET, POST, PUT, OPTIONS, DELETE, etc.
  • headers: Cabeceras que se env√≠an en la solicitud, aqu√≠ puedes ingresar un objeto e incluso una instancia de Headers.
  • body: Datos para enviar en la solicitud, pueden ser un blob, un buffer, form data, string, etc...considera que las solicitudes GET y HEAD no utilizan esta opci√≥n.
  • mode: Modo de env√≠o de la solicitud, puede ser cors, no-cors o  same-origin.
  • credentials: Credenciales que utiliza la petici√≥n. 
  • cache: Indica c√≥mo se debe comportar la solicitud con el cache del navegador.
  • redirect: C√≥mo debe actuar si la respuesta devuelve una redirecci√≥n.

Solicitud GET con Fetch

Es la petición más básica que debes aprender a realizar, este tipo de peticiones es utilizada para obtener información, el siguiente ejemplo trae una lista de objetos:

fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(console.log);

// [{}, {}, {}, ...]
Solicitud GET con Fetch

Solicitud POST con Fetch

La siguiente petición es para enviar información a un servidor, puedes realizarla de la siguiente manera:

fetch("https://reqres.in/api/users", {
  method: "POST",
  body: JSON.stringify({ website: "eldevsin.site" })
})
.then(response => response.json())
.then(console.log);

// {id: "370", createdAt: "2020-04-13T03:40:09.969Z"}
Solicitud POST con Fetch

Nótese que hemos agregado la key body, es allí donde podemos agregar la data que se enviara al server, en este caso en particular está todo ok y la API nos devuelve como respuesta un objeto ficticio.

Utilizando Headers en la petición

Cuando realizas una petici√≥n es muy com√ļn que necesites personalizar la cabecera, y fetch te permite hacerlo de una manera super simple utilizando una instancia de Header, veamos:

const customHeaders = new Headers({
	'User-agent': 'Mozilla/5.0 (PlayStation 4 3.11) AppleWebKit/537.73 (KHTML, like Gecko)'
});

/*
-> lo de arriba es lo mismo que esto:

const customHeaders = {
	'User-agent': 'Mozilla/5.0 (PlayStation 4 3.11) AppleWebKit/537.73 (KHTML, like Gecko)'
};
*/

fetch('https://jsonplaceholder.typicode.com/todos', {
    headers: customHeaders
})
.then(response => response.json())
.then(console.log);
Headers en la solicitud Fetch

Como puedes ver estamos simulando realizar la petici√≥n desde un playstation, pero a√ļn mas importante que eso es que puedes utilizar un objeto literal para construir un header, ¬Ņgenial no?

Enviando un form data con fetch

Este es un caso super utilizado y que con seguridad t√ļ tambi√©n lo necesitar√°s en alg√ļn momento de tu vida como desarrollador, el diferencial aqu√≠ es el dato que enviamos al backend en el body de las opciones.

const formData = new FormData();

formData.append('website', 'elsitesin.site');
formData.append('action', 'follow');

fetch('urldeapi/create', {
    method: 'POST',
    body: formData // mira abajo la explicación :D
})
.then(response => response.json())
.then(console.log);

Enviando Form Data con Fetch

Esta forma de enviar información se utiliza cuando envías por ejemplo una imagen, un video o incluso solo texto cuando tienes la información dentro de la etiqueta form, lo bueno de todo es que FormData te permite apilar la información en forma de clave y valor.

Ahora, quizás de has dado cuenta de que no estamos especificando el tipo de contenido que enviamos, es decir el Content-Type, la razón en este caso puntualmente es que no es necesario, porque es agregado automáticamente al header, el tipo de contenido viaja como "multipart/form-data".

Enviando credenciales en fetch

Este es un caso de uso de igual forma de com√ļn cuando necesitas identificar a tus usuarios, por fortuna es una caracter√≠stica que est√° disponible en fetch y solo basta con agregar una l√≠nea para indicarle si queremos que incluya cookies en la petici√≥n, literalmente:

fetch('https://jsonplaceholder.typicode.com/todos', {
	credentials: 'include'
})
.then(response => response.json())
.then(console.log);
Credenciales en Fetch

La especificación lo deja muy claro, veamos todas las opciones:

  • include: Env√≠a la cookie a cualquier origen de datos, es decir aqu√≠ no importa en qu√© dominio se ejecuta esta acci√≥n.
  • same-origin: Env√≠a la petici√≥n solo si el host solicitado coincide con el origen desde donde se esta ejecutando el script.
  • omit: Por ning√ļn motivo se env√≠an las credenciales.

Configurar CORS en fetch API

CORS viene a ser una característica que resuelve en muchos casos el problema de realizar solicitudes a dominios desconocidos, vulnerabilidades que normalmente son aprovechados por piratas informáticos. A través de la opción mode puedes modificar el comportamiento:

fetch('https://jsonplaceholder.typicode.com/todos', {
	mode: 'cors'
})
.then(response => response.json())
.then(console.log);
CORS en Fetch API

La configuración que definas aquí puede resultar crítico cuando solicitas recursos de otros dominios, por eso ten en cuenta las siguientes opciones:

  • cors: Permite realizar peticiones hacia cualquier origen.
  • no-cors: Permite realizar solicitudes hacia cualquier origen sin embargo la respuesta se oculta para impedir visualizar lo que viene como informaci√≥n.
  • same-origin: Permite realizar las solicitudes hacia el mismo dominio, caso contrario recibes m√°s que seguro un error.

Te cuento algo, muy aparte de poder utilizar esas opciones, tienes la facilidad de poder pasarle a fetch tu propio objeto de Request, de la siguiente forma:

const myConfig = {
  headers: {
    'Content-Type': 'application/json'
  }
}

const request = new Request(
  'https://jsonplaceholder.typicode.com/todos',
  myConfig
);

fetch(request)
  .then(response => response.json())
  .then(console.log)
;
Objeto Request en Fetch API

A eso es que no referíamos con la flexibilidad, considéralo cuando necesites realizar algo similar en tus proyectos web.

Respuesta

El objeto de respuesta básicamente en un objeto del tipo Response, que trae consigo características interesantes para quien sepa aprovecharlas, pues además proporciona gran cantidad de funciones para tratar los tipos de respuesta.

Utilizando la siguiente request como ejemplo:

fetch("https://jsonplaceholder.typicode.com/todos")
	.then(response => console.log(response));
Ejemplo b√°sico

La estructura del objeto que recibes como respuesta es la siguiente:

{
    type: "cors"
    url: "https://jsonplaceholder.typicode.com/todos"
    redirected: false
    status: 200
    ok: true
    statusText: ""
    headers: Headers {}
    body: (...)
    bodyUsed: false
}
Estructura de objeto Response

La mayoría del tiempo solo considerarás utilizar las siguientes propiedades:

  • redirected: Indica si la respuesta que obtienes fue hecha por una redirecci√≥n.
  • status: C√≥digo de estado HTTP.
  • ok: Devuelve un booleano con un true/false para indicar si la respuesta fue exitosa o no.
  • headers: Cabeceras devueltas.
  • body: Informaci√≥n enviada por el servidor del tipo ReadableStream.
  • bodyUsed: Indica si la informaci√≥n del body ya fue le√≠da o no.

¬ŅC√≥mo leer el contenido del body?

Como lo mencionamos anteriormente, obtenemos una respuesta de una instancia del tipo Response, por lo que tenemos acceso a ciertos métodos que nos resuelven la información de acuerdo al tipo de dato que vamos a obtener.

En nuestro ejemplo b√°sico tenemos lo siguiente:

fetch("https://jsonplaceholder.typicode.com/todos/1")
	.then(response => response.json())
	.then(console.log)
;
Ejemplo b√°sico

Se apreciar muy bien que ejecutamos la función .json(), que obviamente indica que la data que queremos ver es de tipo JSON, y devuelve una promesa que debe completarse hasta que termine de formatear la información llegada del backend, el resultado debe ser similar a lo siguiente:

{userId: 1, id: 1, title: "delectus aut autem", completed: false}
Respuesta de ejemplo

Pero esa no es la √ļnica funci√≥n que tenemos disponible, existen otras m√°s que debes conocer y que te ahorrar√°n muchos dolores de cabeza en el futuro:

  • clone(): Devuelve una copia del objeto Response.
  • blob(): Devuelve una promesa y al terminar de obtener la informaci√≥n del servidor resuelve un objeto del tipo Blob.
  • json(): Devuelve una promesa y al terminar de obtener la informaci√≥n del servidor resuelve un objeto del tipo JSON.
  • text(): Devuelve una promesa y al terminar de obtener la informaci√≥n del servidor resuelve un objeto del tipo TEXT.
  • arrayBuffer(): Devuelve una promesa y al terminar de obtener la informaci√≥n del servidor resuelve un objeto del tipo ArrayBuffer.
  • formData(): Devuelve una promesa y al terminar de obtener la informaci√≥n del servidor resuelve un objeto del tipo FormData.

Ejemplo para monitorear el progreso de una solicitud

Por motivos de usabilidad es primordial informar al usuario cual es el porcentaje de progreso que va completando una solicitud, sobretodo si es que estamos mostrando recursos como im√°genes, vamos a ver un ejemplo sencillo de como podemos monitorear el avance de una solicitud utilizando la instancia de la clase ReadableStreams.

Definiendo el lector de progreso

Esta función debe permitir dos cosas, primero, recibir una función que será ejecutada cafa vez que se recibe trozos de información y segundo, nos permita manipular el objeto Response.

function progressReader(onProgress) { // 1
  return response => { // 2
    if (!response.body) return response;

    let loaded = 0;
    const contentLength = response.headers.get("content-length"); // 3
    const total = !contentLength ? -1 : parseInt(contentLength, 10);

    const readable = new ReadableStream({
      start(controller) {
        const reader = response.body.getReader(); // 4
        read(); // 5

        function read() {
          return reader
            .read() // 6
            .then(({ done, value }) => {
              if (done) return controller.close(); // 7
              loaded += value.byteLength;
              onProgress({ loaded, total }); // 8
              controller.enqueue(value);
              return read(); // 9
            })
            .catch(error => {
              console.error(error);
              controller.error(error);
            });
        }
      }
    });

    return new Response(readable); // 10
  };
}
Lector de progreso solicitud Fetch

El proceso es un poco complejo, así que lo dividiremos en dos partes.

Primero revisamos los pasos que tienen que ver con la forma que construimos la función.

  1. Declaramos una función que espera un callback que servirá para notificar cada vez que reciba un trozo de información.
  2. Devuelve un closure que se usar√° para al momento que se resuelve la promesa, esta recibir√° el objeto Response.
  3. Obtenemos la longitud del contenido de la imagen.

La siguiente parte ya va un poco más a lo duro del ejercicio, jugar con Streams, vamos a intentar hacerlo simple y entendible, para eso considera que el constructor del objeto ReadableStream, espera que le pases un objeto con ciertas opciones, en este caso, usaremos clave start para configurar cómo queremos que se comporte el lector de streams.

4.  Obtenemos el lector de streams.

5.  Ejecutamos por primera vez la función personalizada que empezará a leer los trozos de datos.

6.  La función read() retorna una promesa que se resuelve cuando se recibe información. Al ejecutar nuestra función nos envían dos parámetros done(si ya termino de leer) y value(trozo de información).

7. ¬†Si la informaci√≥n se ha terminado de leer, entonces "cierra el ca√Īo", es decir, indicar que la transmisi√≥n concluy√≥.

8.  Enviamos al console.log la cantidad de datos leídos hasta el momento y el total de datos de la imagen.

9.  Ejecuta nuevamente read() para recibir el siguiente trozo de información, y repite el proceso hasta que ya no tenga más datos que recibir y caiga en el punto 7.

10.  Devuelve un objeto Response con el reader que acabamos de configurar.

Con eso ya estamos listos para implementarlo en nuestras solicitudes con fetch, sigamos con la siguiente parte:

Inyectanto el lector de Streams

const streamProcessor = progressReader(console.log); // 11

fetch("https://fetch-progress.anthum.com/20kbps/images/sunrise-progressive.jpg")
  .then(streamProcessor) // 12
  .then(response => response.blob()) // 13
  .then(blobData => {
    // Blob data
  });
Inyectando lector de Streams

11. Ejecutamos la función progressReader que acabamos de crear y le pasamos console.log(que recuerda que también es una función), esta se encargará de informarnos el porcentaje de avance cada vez que un trozo de información es recibido, esta nos devolverá a su vez una nueva función que por ahora se almacena en la variable streamProcessor.

12. "Inyectamos" streamProcessor en el primer then() para que reciba un objeto response y devuelva nuestro objeto Response (ya modificado).

13. Finalmente como esperamos un recurso de tipo imagen, debemos utilizar la función blob() para leer dicha información.

Después de eso ya tienes disponible el recurso para insertarlo en cualquier parte de tu frontend.

Soporte

De qué vale ver tanta magia si no se puede utilizar, eso es lo que probablemente estarás pensando si no conoces el tiempo que se ha venido puliendo esta característica, pues déjame quitarte un peso de encima y miremos la siguiente tabla que nos brinda más información acerca de la compatibilidad actual:

Tabla de compatibilidad Fetch API

El 94.99% de navegadores soportan fetch API sin problemas, excepto por IE11 y Opera mini, que siempre andan juntos, sin embargo para que no te sientas mal aquí hay un polyfill que salva al menos a IE, por tanto, estos resultados de compatibilidad son más que positivos y dan la confianza para empezar a utilizar Fetch API de una buena vez, utilízalo para llamar a servidores PHP, Node, Java...o lo que se te ocurra. Yeah!!

Lo obligatorio (espero que para ti también)

D√©jame saber que te pareci√≥ este post en los comentarios y cu√©ntame si tienes ya con alguna experiencia utilizando fetch, adem√°s si consideras que este mega post te ha aportado un grandioso valor, comp√°rtelo con tus amigos, no dejes de seguirnos en nuestras redes sociales y ap√ļntate a las notificaciones para ser el primero en enterarte de nuestro siguiente post...nos vemos en una pr√≥xima entrega.

GIPHY

Fernando Palacios

¡Hoy es un gran día para hacer grandes cosas!

¡Genial! Te has suscrito con éxito.
¬°Genial! Ahora, completa el checkout para tener acceso completo.
¡Bienvenido de nuevo! Has iniciado sesión con éxito.
√Čxito! Su cuenta est√° totalmente activada, ahora tienes acceso a todo el contenido.