0% encontró este documento útil (0 votos)
59 vistas

LLamadas Asincronas en Javascript

El documento explica las llamadas asíncronas en JavaScript. Describe que JavaScript es un lenguaje no bloqueante que permite realizar tareas asíncronas concurrentemente sin bloquear la ejecución del resto del código. Explica que las funciones callbacks son una forma de gestionar la asincronía y da ejemplos como setTimeout. También menciona promesas, async/await y top-level await como otras formas de gestionar la asincronía.

Cargado por

gunsndroses
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
59 vistas

LLamadas Asincronas en Javascript

El documento explica las llamadas asíncronas en JavaScript. Describe que JavaScript es un lenguaje no bloqueante que permite realizar tareas asíncronas concurrentemente sin bloquear la ejecución del resto del código. Explica que las funciones callbacks son una forma de gestionar la asincronía y da ejemplos como setTimeout. También menciona promesas, async/await y top-level await como otras formas de gestionar la asincronía.

Cargado por

gunsndroses
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 16

LLamadas asíncronas en javascript

La asincronía es uno de los conceptos principales que rige el mundo de Javascript. Cuando
comenzamos a programar, normalmente realizamos tareas de forma síncrona, llevando a cabo tareas
secuenciales que se ejecutan una detrás de otra, de modo que el orden o flujo del programa es
sencillo y fácil de observar en el código:
primera_funcion(); // Tarea 1: Se ejecuta primero
segunda_funcion(); // Tarea 2: Se ejecuta cuando termina primera_funcion()
tercera_funcion(); // Tarea 3: Se ejecuta cuando termina segunda_funcion()

Sin embargo, en el mundo de la programación, tarde o temprano necesitaremos realizar operaciones


asíncronas, especialmente en ciertos lenguajes como Javascript, donde tenemos que realizar tareas
que tienen que esperar a que ocurra un determinado suceso que no depende de nosotros, y
reaccionar realizando otra tarea sólo cuando dicho suceso ocurra.

Lenguaje no bloqueante

Cuando hablamos de Javascript, habitualmente nos referimos a él como un lenguaje no bloqueante.


Con esto queremos decir que las tareas que realizamos no se quedan bloqueadas esperando ser
finalizadas, y, por consiguiente, evitando proseguir con el resto de tareas.

Imaginemos que la segunda_funcion() del ejemplo anterior realiza una tarea que depende de otro
factor, como por ejemplo un click de ratón del usuario. Si hablásemos de un lenguaje bloqueante,
hasta que el usuario no haga click, Javascript no seguiría ejecutando las demás funciones, sino que se
quedaría bloqueado esperando a que se terminase esa segunda tarea:

Pero como Javascript es un lenguaje no bloqueante, lo que hará es mover esa tarea a una lista de
tareas pendientes a las que irá «prestándole atención» a medida que lo necesite, pudiendo continuar
y retomar el resto de tareas a continuación de la segunda.
¿Qué es la asincronía?

Pero esto no es todo. Ten en cuenta que pueden existir múltiples tareas asíncronas, dichas tareas
puede que terminen realizándose correctamente (o puede que no) y ciertas tareas pueden depender de
otras, por lo que deben respetar un cierto orden. Además, es muy habitual que no sepamos
previamente cuanto tiempo va a tardar en terminar una tarea, por lo que necesitamos un mecanismo
para controlar todos estos factores: las promesas, las cuales veremos algunos capítulos más adelante.

Ejemplos de tareas asíncronas

En Javascript no todas las tareas son asíncronas, pero hay ciertas tareas que si lo son, y
probablemente se entiendan mejor con ejemplos reales:

 Un fetch() a una URL para obtener un archivo .json.


 Un new Audio() de una URL con un .mp3 al que se hace .play() para reproducirlo.
 Una tarea programada con setTimeout() que se ejecutará en el futuro.
 Una comunicación desde Javascript a la API del sintetizador de voz del navegador.
 Una comunicación desde Javascript a la API de un sensor del smartphone.

Todos estos ejemplos se realizan mediante tareas asíncronas, ya que realizan un procedimiento que
podría bloquear la ejecución del resto del programa al tardar mucho: la descarga de un fichero grande
desde un servidor lento, una conexión a internet muy lenta, un dispositivo saturado a la hora de
comunicarse con el sensor del móvil, etc...

¿Cómo gestionar la asincronía?

Teniendo en cuenta el punto anterior, debemos aprender a buscar mecanismos para dejar claro en
nuestro código Javascript, que ciertas tareas tienen que procesarse de forma asíncrona para quedarse
a la espera, y otras deben ejecutarse de forma síncrona.

En Javascript existen varias formas de gestionar la asincronía, donde quizás las más populares son
las siguientes (que iremos viendo y profundizando en cada artículo de este tema):

Método Descripción Tema


Probablemente, la forma más clásica de gestionar la
Mediante callbacks Ver Callbacks
asincronía en Javascript.
Una forma más moderna y actual de gestionar la
Mediante promesas Ver Promesas
asincronía.
Seguimos con promesas, pero con async/await añadimos Ver
Mediante async/await
más azúcar sintáctico. Async/Await
Mediante top-level Una variación de la anterior, donde no es necesario usar async en
await determinados contextos.

Iremos viendo la implementación de cada una de ellas en cada capítulo de este tema, utilizando de
base un ejemplo sencillo donde lanzamos un dado un número concreto de veces, que explicaremos
a continuación.
Ejemplo base

Tenemos un array numbers, en el cuál insertaremos números. Insertaremos la cantidad de números


que figura en iterations. En cada iteración se insertará un number, que habrá sido generado con
una simulación del lanzamiento de un dado (un número aleatorio del 1 al 6). En el caso de obtener
un 6, paramos y rompemos el bucle:

const iterations = 10;


const numbers = [];

for (let i = 0; i < iterations; i++) {


const number = 1 + Math.floor(Math.random() * 6);
numbers.push(number);
if (number === 6) {
console.log("ERROR");
break;
}
}

console.log(numbers);

Al terminar este fragmento de código, tendremos un array numbers que contendrá todos los números
obtenidos en los lanzamientos de los dados, es decir, 10 números si no hemos obtenido ningún 6. Si
hemos obtenido un 6 puede que tengamos menos números, debido a que se rompe el bucle tras
insertarlo.

Ten en cuenta que este ejemplo es una tarea síncrona (aún no existe asincronía). Simplemente
estamos explicando el ejemplo que usaremos de base en los siguientes capítulos del tema para
controlar asincronía. Quizás lo ideal sería que cada lanzamiento del lado tardase un tiempo concreto
en dar la respuesta, pero no se ha introducido ese retardo para simplificar los ejemplos de código.

Funciones Callbacks

Los callbacks (a veces denominados funciones de retrollamada o funciones callback) no son más
que un tipo de funciones que se pasan por parámetro a otras funciones. El objetivo de esto es tener
una forma más legible de escribir funciones, más cómoda y flexible para reutilizarlas, y además entra
bastante en consonancia con el concepto de asincronía de Javascript, como veremos más adelante.

Ten en cuenta que actualmente, controlar la asincronía sólo mediante callbacks puede ser una
práctica obsoleta. Es preferible utilizar promesas, que generalmente es más adecuado.

¿Qué es un callback?

Como hemos dicho, las funciones callback no son más que un tipo de funciones que se pasan por
parámetro a otras funciones. Además, los parámetros de dichas funciones toman un valor especial en
el contexto del interior de la función.

Pero veamos un ejemplo. Imaginemos el siguiente bucle tradicional para recorrer un :


const list = ["A", "B", "C"];

for (let i = 0; i < list.length; i++) {


console.log("i=", i, " list=", list[i]);
}

En i tenemos la posición del array que estamos recorriendo (va de 0 a 2) y con list[i] accedemos
a la posición del array para obtener el elemento, es decir, desde A hasta C. Ahora veamos, como
podemos hacer este mismo bucle utilizando el método forEach() del al cuál le pasamos una función
callback:

list.forEach(function(e,i) {
console.log("i=", i, "list=", e);
});

Esto se puede reescribir como:

["A", "B", "C"].forEach((e,i) => console.log("i=", i, "list=", e));

Lo importante de este ejemplo es que se vea que la función callback que le hemos pasando a
forEach() se va a ejecutar por cada uno de los elementos del array, y en cada iteración de dicha
función callback, los parámetros e, i van a tener un valor especial:

 e es el elemento del array


 i es el índice (posición) del array

Callbacks en Javascript

Una vez entendido esto, vamos a profundizar un poco con las funciones callbacks utilizadas para
realizar tareas asíncronas. Probablemente, el caso más fácil de entender es utilizar un temporizador
mediante la función setTimeout(callback, time).

Dicha función nos exige dos parámetros:

 La función callback a ejecutar


 El tiempo time que esperará antes de ejecutarla

Así pues, el ejemplo sería el siguiente:

setTimeout(function() {
console.log("He ejecutado la función");
}, 2000);

Simplemente, le decimos a setTimeout() que ejecute la función callback que le hemos pasado por
primer parámetro cuando transcurran 2000 milisegundos (es decir, 2 segundos). Utilizando arrow
functions se puede simplificar el callback y hacer mucho más «fancy» y legible:

setTimeout(() => console.log("He ejecutado la función"), 2000);

Si lo prefieres y lo ves más claro (no suele ser habitual en código Javascript, pero cuando se
empieza suele resultar más fácil entenderlo) podemos guardar el callback en una constante:
const action = () => console.log("He ejecutado la función");
setTimeout(action, 2000);

En cualquiera de los casos, lo importante es darse cuenta que estamos usando una función callback
para pasársela a setTimeout(), que es otra función. En este caso, se trata de «programar» un suceso
que ocurrirá en un momento conocido del futuro, pero muchas veces desconoceremos cuando se
producirá (o incluso si se llegará a producir).

Si probamos el código que verás a continuación, comprobarás que el segundo console.log() se


ejecutará antes que el primero, dentro del setTimeout(), mostrando primero Código síncrono y
luego Código asíncrono en la consola del navegador:

setTimeout(() => console.log("Código asíncrono."), 2000);


console.log("Código síncrono.");

El último console.log del código se ejecuta primero (forma parte del flujo principal de ejecución
del programa). El setTimeout() que figura en una línea anterior, aunque se ejecuta antes, pone en
espera a la función callback, que se ejecutará cuando se cumpla una cierta condición (transcurran 2
segundos desde ese momento).

Esto puede llegar a sorprender a desarrolladores que llegan de otros lenguajes considerados
bloqueantes; Javascript sin embargo se considera un lenguaje asíncrono y no bloqueante. ¿Qué
significa esto? Al ejecutar la línea del setTimeout(), el programa no se queda bloqueado esperando
a que terminen los 2 segundos y se ejecute la función callback, sino que continúa con el flujo general
del programa para volver más adelante cuando sea necesario a ejecutar el callback, aprovechando
así mejor el tiempo y realizando tareas de forma asíncrona.

Asincronía con callbacks

Las funciones callback pueden utilizarse como un primer intento de manejar la asincronía en un
programa. De hecho, eran muy utilizadas en la época dorada de jQuery, donde muchas funciones o
librerías tenían una estructura similar a esta (en jQuery se usaba algo similar):

function doTask(number, callback) {


/* Código de la función */
}

doTask(42, function(err, result) {


/* Trabajamos con err o result según nos interese */
});

Observa que doTask() es la función que realiza la tarea en cuestión. Puede tener los parámetros que
se consideren adecuados, como cualquier otra función, la diferencia es que establecemos un
callback que usaremos para controlar lo que se debe hacer.

Más adelante, llamamos a la función doTask() y en su parámetro callback pasamos una función
con dos parámetros; err y result. El primero de ellos, err, utilizado para controlar un error y el
segundo de ellos, result, utilizado para manejar los valores devueltos.
Vamos a utilizar el ejemplo del lanzamiento de 10 dados que explicamos en el primer capítulo de
Asincronía, para adaptarlo a funciones callbacks. En primer lugar, veamos la implementación de la
función doTask:

/* Implementación con callbacks */


const doTask = (iterations, callback) => {
const numbers = [];
for (let i = 0; i < iterations; i++) {
const number = 1 + Math.floor(Math.random() * 6);
numbers.push(number);
if (number === 6) {
/* Error, se ha sacado un 6 */
callback({
error: true,
message: "Se ha sacado un 6"
});
return;
}
}
/* Termina bucle y no se ha sacado 6 */
return callback(null, {
error: false,
value: numbers
});
}

Como se puede ver, estamos utilizando arrow functions para definir la función doTask(). Le
pasamos un parámetro iterations que simplemente indica el número de iteraciones que tendrá el
bucle (número de lanzamientos del dado). Por otro lado, el segundo parámetro es nuestro callback,
que recordemos que es una función, por lo que podremos ejecutarla en momentos concretos de
nuestro código. Lo hacemos en dos ocasiones:

 En el if cuando number es 6 (detectamos como error cuando obtenemos un 6). Le pasamos


un objeto por parámetro que contiene un error y message, el mensaje de error.
 Tras el for, con dos parámetros. El primero , ya que en este caso no hay error. El segundo
parámetro un objeto que contiene un campo value con el array de resultados.

Teniendo claro esto, veamos la llamada a la función doTask(), donde le pasamos esa función
callback e implementamos el funcionamiento, que en nuestro caso serán dos simples
console.error() y console.log():

doTask(10, function(err, result) {


if (err) {
console.error("Se ha sacado un ", err.message);
return;
}
console.log("Tiradas correctas: ", result.value);
});

Esto es una forma clásica donde utilizamos una función callback para gestionar la asincronia y
facilitar la reutilización, pudiendo reutilizar la función con la lógica, aplicando diferentes funciones
callback según nos interese.
Observa que aunque en este ejemplo se ha utilizado un parámetro err y otro result en el callback
para gestionar un objeto de error y un objeto de resultados, esto puede modificarse a gusto del
desarrollador, aunque lo habitual suele ser este esquema.

Desventajas de los callbacks

A pesar de ser una forma flexible y potente de controlar la asincronía, que permite realizar múltiples
posibilidades, las funciones callbacks tienen ciertas desventajas evidentes. En primer lugar, el
código creado con las funciones es algo caótico y (quizás subjetivamente) algo feo. Por ejemplo, el
tener que pasar un por parámetros en algunas funciones, no es demasiado elegante.

Pero sobre todo, uno de los problemas evidentes viene a la hora de tener que gestionar la asincronía
varias veces en una misma función, donde al introducir varias funciones con callbacks en su interior,
conseguimos una estructura anidada similar a la siguiente:

La forma triangular que produce es conocida como Callback Hell o Pyramid of Doom, debido a su
forma, resultando un código muy poco elegante que se puede complicar demasiado de cara a la
legibilidad. Es cuando entran en juego las promesas.

¿Qué son las promesas?

Las promesas son un concepto para resolver el problema de asincronía de una forma mucho más
elegante y práctica que, por ejemplo, utilizando funciones callbacks directamente.

Como su propio nombre indica, una promesa es algo que, en principio pensamos que se cumplirá,
pero en el futuro pueden ocurrir varias cosas:
 La promesa se cumple (promesa resuelta)
 La promesa no se cumple (promesa se rechaza)
 La promesa se queda en un estado incierto indefinidamente (promesa pendiente)

Con estas sencillas bases, podemos entender el funcionamiento de una promesa en Javascript. Antes
de empezar, también debemos tener claro que existen dos partes importantes de las promesas: como
consumirlas (utilizar promesas) y como crearlas (preparar una función para que use promesas y se
puedan consumir).

Promesas en Javascript

Las promesas en Javascript se representan a través de un , y cada promesa estará en un estado


concreto: pendiente, aceptada o rechazada. Además, cada promesa tiene los siguientes métodos,
que podremos utilizar para utilizarla:

Métodos Descripción
.then(resolve) Ejecuta la función callback resolve cuando la promesa se cumple.
.catch(reject) Ejecuta la función callback reject cuando la promesa se rechaza.
.then(resolve,reject) Método equivalente a las dos anteriores en el mismo .then().
.finally(end) Ejecuta la función callback end tanto si se cumple como si se rechaza.

Más adelante veremos, que a diferencia del apartado anterior donde se utilizaban solamente
funciones callback, en este enfoque se tiende a no anidar promesas, evitando así el famoso
Callback Hell, y haciendo el código mucho más legible.

Consumir una promesa

La forma general de consumir una promesa es utilizando el .then() con un sólo parámetro, puesto
que muchas veces lo único que nos interesa es realizar una acción cuando la promesa se cumpla:

fetch("/robots.txt").then(function(response) {
/* Código a realizar cuando se cumpla la promesa */
});
Lo que vemos en el ejemplo anterior es el uso de la función fetch(), la cuál devuelve una promesa
que se cumple cuando obtiene respuesta de la petición realizada. De esta forma, estaríamos
preparando (de una forma legible) la forma de actuar de nuestro código a la respuesta de la petición
realizada, todo ello de forma asíncrona.

Recuerda que podemos hacer uso del método .catch() para actuar cuando se rechaza una promesa:

fetch("/robots.txt")
.then(function(response) {
/* Código a realizar cuando se cumpla la promesa */
})
.catch(function(error) {
/* Código a realizar cuando se rechaza la promesa */
});

Observa como hemos indentado los métodos .then() y .catch(), ya que se suele hacer así para
que sea mucho más legible para el. Además, se pueden encadenar varios .then() si se siguen
generando promesas y se devuelven con un return:

fetch("/robots.txt")
.then(response => {
return response.text(); // Devuelve una promesa
})
.then(data => {
console.log(data);
})
.catch(error => { /* Código a realizar cuando se rechaza la promesa
*/ });

No olvides indicar el return para poder encadenar las siguientes promesas con .then(). Tras un
.catch() también es posible encadenar .then() para continuar procesando promesas.

De hecho, usando arrow functions se puede mejorar aún más la legibilidad de este código,
recordando que cuando sólo tenemos una sentencia en el cuerpo de la arrow function hay un
return implícito:

fetch("/robots.txt")
.then(response => response.text())
.then(data => console.log(data))
.finally(() => console.log("Terminado."))
.catch(error => console.error(data));

Observese además que hemos añadido el método .finally() para añadir una función callback que
se ejecutará tanto si la promesa se cumple o se rechaza, lo que nos ahorrará tener que repetir la
función en el .then() como en el .catch().

En todo este apartado hemos visto como utilizar o consumir una promesa haciendo uso de .then(),
que es lo que en la mayoría de los casos necesitaremos. Sin embargo, vamos a ver en el siguiente
apartado como crear o implementar las promesas para su posterior consumo.
Asincronía con promesas

Vamos a implementar el ejercicio base que hemos comentado en el primer capítulo de este tema
utilizando promesas. Observa que lo primero que haremos es crear un nuevo objeto que «envuelve»
toda la función de la tarea doTask().

Al new Promise() se le pasa por parámetro una función con dos callbacks, el primero resolve el
que utilizaremos cuando se cumpla la promesa, y el segundo reject cuando se rechace:

/* Implementación con promesas */


const doTask = (iterations) => new Promise((resolve, reject) => {
const numbers = [];
for (let i = 0; i < iterations; i++) {
const number = 1 + Math.floor(Math.random() * 6);
numbers.push(number);
if (number === 6) {
reject({
error: true,
message: "Se ha sacado un 6"
});
}
}
resolve({
error: false,
value: numbers
});
});

Como ves, se trata de una implementación muy similar a los callbacks que vimos en el apartado
anterior, pero utilizan una nativa para poder luego consumirla cómodamente:

doTask(10)
.then(result => console.log("Tiradas correctas: ", result.value))
.catch(err => console.error("Ha ocurrido algo: ", err.message));

Imagina el caso de que cada lanzamiento del dado (la parte donde genera el número aleatorio)
fuera un proceso más costoso que tardara un tiempo considerable, quizás de esa forma se vea más
clara la necesidad de una tarea asíncrona, controlada con promesas.

En el siguiente capítulo veremos como trabajar con múltiples promesas y hacer acciones compuestas
con varias de ellas.

Para profundizar mas sobre promesas: https://lenguajejs.com/javascript/asincronia/promise-api/

Vamos en centrar en realizar llamadas a url de manera asíncrona:

Fetch: Peticiones Asíncronas


Fetch es el nombre de una nueva API para Javascript con la cuál podemos realizar peticiones HTTP
asíncronas utilizando promesas y de forma que el código sea un poco más sencillo y menos verbose.
La forma de realizar una petición es muy sencilla, básicamente se trata de llamar a fetch y pasarle
por parámetro la URL de la petición a realizar:
// Realizamos la petición y guardamos la promesa
const request = fetch("/robots.txt");

// Si es resuelta, entonces ejecuta esta función...


request.then(function(response) { ... });

El fetch() devolverá una que será aceptada cuando reciba una respuesta y sólo será rechazada si
hay un fallo de red o si por alguna razón no se pudo completar la petición. El modo más habitual de
manejar las promesas es utilizando .then(). Esto se suele reescribir de la siguiente forma, que
queda mucho más simple:

fetch("/robots.txt")
.then(function(response) {
/** Código que procesa la respuesta **/
});

Al método .then() se le pasa una función callback donde su parámetro response es el objeto de
respuesta de la petición que hemos realizado. En su interior realizaremos la lógica que queramos
hacer con la respuesta a nuestra petición. A la función fetch(url, options) se le pasa por
parámetro la url de la petición y, de forma opcional, un objeto options con opciones de la petición
HTTP.

Vamos a examinar un código donde veamos un poco mejor como hacer la petición con fetch:

// Opciones de la petición (valores por defecto)


const options = {
method: "GET"
};

// Petición HTTP
fetch("/robots.txt", options)
.then(response => response.text())
.then(data => {
/** Procesar los datos **/
});

Un poco más adelante, veremos como trabajar con la respuesta response, pero vamos a centrarnos
ahora en el parámetro opcional options de la petición HTTP. En este objeto podemos definir varios
detalles:

Campo Descripción
method Método HTTP de la petición. Por defecto, GET. Otras opciones: HEAD, POST, etc...
body
Cuerpo de la petición HTTP. Puede ser de varios tipos: String, FormData, Blob,
etc...
headers Cabeceras HTTP. Por defecto, {}.
credentials Modo de credenciales. Por defecto, omit. Otras opciones: same-origin e include.

Lo primero, y más habitual, suele ser indicar el método HTTP a realizar en la petición. Por defecto,
se realizará un GET, pero podemos cambiarlos a HEAD, POST, PUT o cualquier otro tipo de método. En
segundo lugar, podemos indicar objetos para enviar en el body de la petición, así como modificar las
cabeceras en el campo headers:
const options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(jsonData)
};

Por último, el campo credentials permite modificar el modo en el que se realiza la petición. Por
defecto, el valor omit hace que no se incluyan credenciales en la petición, pero es posible indicar los
valores same-origin, que incluye las credenciales si estamos sobre el mismo dominio, o include
que incluye las credenciales incluso en peticiones a otros dominios.

Recuerda que estamos realizando peticiones relativas al mismo dominio. En el caso de realizar
peticiones a dominios diferentes obtendríamos un problema de CORS (Cross-Origin Resource
Sharing) similar al siguiente:

Access to fetch at 'https://otherdomain.com/file.json' from origin 'https://domain.com/' has been


blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the
resource with CORS disabled.

Más adelante hablaremos de CORS y de como solucionar estos problemas si necesitamos realizar
peticiones a otros dominios.

Cabeceras (Headers)

Aunque en el ejemplo anterior hemos creado las cabeceras como un genérico de Javascript, es
posible crear un objeto Headers con el que trabajar:

const headers = new Headers();


headers.set("Content-Type", "application/json");
headers.set("Content-Encoding", "br");

Para ello, a parte del método .set() podemos utilizar varios otros para trabajar con cabeceras,
comprobar su existencia, obtener o cambiar los valores o incluso eliminarlos:

Método Descripción
.has(name) Comprueba si la cabecera name está definida.
.get(name) Obtiene el valor de la cabecera name.
.set(name, value) Establece o modifica el valor value a la cabecera name.
.append(name, value) Añade un nuevo valor value a la cabecera name.
.delete(name) Elimina la cabecera name.

Como muchos otros objetos iterables, podemos utilizar los métodos .entries(), .keys() y/o
.values() para recorrerlos:

for ([key, value] of headers.entries()) {


console.log("Clave: ", key, "valor: ", value);
}
Para peticiones con pocas cabeceras no es mayor problema, pero en peticiones más complejas
utilizar Headers es una buena práctica.

Respuesta de la petición HTTP

Si volvemos a nuestro ejemplo de la petición con fetch, observaremos que en el primer .then()
tenemos un objeto response. Se trata de la respuesta que nos llega del servidor web al momento de
recibir nuestra petición:

// Petición HTTP
fetch("/robots.txt", options)
.then(response => response.text())
.then(data => {
/** Procesar los datos **/
});

Aunque en este ejemplo, simplemente estamos utilizando una arrow function que hace un return
implícito de la promesa que devuelve el método .text(), dicho objeto response tiene una serie de
propiedades y métodos que pueden resultarnos útiles al implementar nuestro código.

Por el lado de las propiedades, tenemos las siguientes:

Propiedad Descripción
.status Código de error HTTP de la respuesta (100-599).
.statusText Texto representativo del código de error HTTP anterior.
.ok Devuelve true si el código HTTP es 200 (o empieza por 2).
.headers Cabeceras de la respuesta.
.url URL de la petición HTTP.

Si venimos de XMLHttpRequest, esto no nos resultará nada extraño. Las propiedades .status y
statusText nos devuelven el código de error HTTP de la respuesta en formato numérico y cadena
de texto respectivamente.

Sin embargo, existe una novedad respecto a XHR, y es que tenemos una propiedad .ok que nos
devuelve true si el código de error de la respuesta es un valor del rango 2xx, es decir, que todo ha
ido correctamente. Así pues, tenemos una forma práctica y sencilla de comprobar si todo ha ido bien
al realizar la petición:

fetch("/robots.txt")
.then(response => {
if (response.ok)
return response.text()
})

Por último, tenemos la propiedad .headers que nos devuelve las cabeceras de la respuesta y la
propiedad .url que nos devuelve la URL completa de la petición que hemos realizado.
Métodos de procesamiento

Por otra parte, la instancia response también tiene algunos métodos interesantes, la mayoría de ellos
para procesar mediante una promesa los datos recibidos y facilitar el trabajo con ellos:

Método Descripción
.text() Devuelve una promesa con el texto plano de la respuesta.
.json()
Idem, pero con un objeto json. Equivalente a usar
JSON.parse().
.blob() Idem, pero con un objeto Blob (binary large object).
.arrayBuffer() Idem, pero con un objeto ArrayBuffer (buffer binario puro).
.formData() Idem, pero con un objeto FormData (datos de formulario).
.clone() Crea y devuelve un clon de la instancia en cuestión.
Response.error()
Devuelve un nuevo objeto Response con un error de red
asociado.
Response.redirect(url, code) Redirige a una url, opcionalmente con un code de error.

Observa que en los ejemplos anteriores hemos utilizado response.text(). Este método indica que
queremos procesar la respuesta como datos textuales, por lo que dicho método devolverá una con los
datos en texto plano, facilitando trabajar con ellos de forma manual:

fetch("/robots.txt")
.then(response => response.text())
.then(data => console.log(data));

Observa que en este fragmento de código, tras procesar la respuesta con response.text(),
devolvemos una con el contenido en texto plano. Esta se procesa en el segundo .then(), donde
gestionamos dicho contenido almacenado en data.

Ten en cuenta que tenemos varios métodos similares para procesar las respuestas. Por ejemplo, el
caso anterior utilizando el método response.json() en lugar de response.text() sería
equivalente al siguiente fragmento:

fetch("/contents.json")
.then(response => response.text())
.then(data => {
const json = JSON.parse(data);
console.log(json);
});

Como se puede ver, con response.json() nos ahorraríamos tener que hacer el JSON.parse() de
forma manual, por lo que el código es algo más directo.

Ejemplo utilizando promesas

Lo que vemos a continuación sería un ejemplo un poco más completo de todo lo que hemos visto
hasta ahora:

 Comprobamos que la petición es correcta con response.ok


 Utilizamos response.text() para procesarla
 En el caso de producirse algún error, lanzamos excepción con el código de error
 Procesamos los datos y los mostramos en la consola
 En el caso de que la sea rechazada, capturamos el error con el catch

// Petición HTTP
fetch("/robots.txt")
.then(response => {
if (response.ok)
return response.text()
else
throw new Error(response.status);
})
.then(data => {
console.log("Datos: " + data);
})
.catch(err => {
console.error("ERROR: ", err.message)
});

De hecho, podemos refactorizar un poco este ejemplo para hacerlo más legible. Creamos la función
isResponseOk() para procesar la respuesta (invirtiendo el condicional para hacerlo más directo).
Luego, los .then() y .catch() los utilizamos con una arrow function para simplificarlos:

const isResponseOk = (response) => {


if (!response.ok)
throw new Error(response.status);
return response.text()
}

fetch("/robots.txt")
.then(response => isResponseOk(response))
.then(data => console.log("Datos: ", data))
.catch(err => console.error("ERROR: ", err.message));

Sin embargo, aunque es bastante común trabajar con promesas utilizando .then(), también
podemos hacer uso de async/await para manejar promesas, de una forma un poco más directa.

Ejemplo utilizando Async/await

Utilizar async/await no es más que lo que se denomina azúcar sintáctico, es decir, utilizar algo
visualmente más agradable, pero que por debajo realiza la misma tarea. Para ello, lo que debemos
tener siempre presente es que un await sólo se puede ejecutar si esta dentro de una función definida
como async.

En este caso, lo que hacemos es lo siguiente:

 Creamos una función request(url) que definimos con async


 Llamamos a fetch utilizando await para esperar y resolver la promesa
 Comprobamos si todo ha ido bien usando response.ok
 Llamamos a response.text() utilizando await y devolvemos el resultado

const request = async (url) => {


const response = await fetch(url);
if (!response.ok)
throw new Error("WARN", response.status);
const data = await response.text();
return data;
}

const resultOk = await request("/robots.txt");


const resultError = await request("/nonExistentFile.txt");
Una vez hecho esto, podemos llamar a nuestra función request y almacenar el resultado, usando
nuevamente await. Al final, utilizar .then() o async/await es una cuestión de gustos y puedes
utilizar el que más te guste.

También podría gustarte