Explicaciones y ejemplos de las sentencias async await en Javascript. Qué es async y await, para qué sirven, qué utilidad tienen y cómo se usan para mejorar el código asíncrono.
Una de las características más importantes de JavaScript es su comportamiento asíncrono. Actualmente la mayor parte de los lenguajes tienen características asíncronas, porque se van copiando los unos a los otros, sin embargo JavaScript nació con la asincronía como una de sus características iniciales.
Decimos generalmente que javaScript es un lenguaje fácil, pero justamente por su carácter asíncrono a veces resulta un poco difícil llegar a dominarlo. En otros artículos anteriores de desarrollo web hemos hablado bastante sobre la asincronía y explicado con detalle de qué se trata y cómo se debe de gestionar. En este artículo vamos a tratar un tema un poquito más avanzado que es el async await de JavaScript.
Qué es async await
En JavaScript existen varios modos de tratarla a sincronía. El modo de más alto nivel es async / await, que nos permite un código más claro y más parecido al código síncrono.
Se trata básicamente de una manera especial de tratar las promesas en Javascript, evitando tener que crearlas manualmente y escribir los bloques then
y catch
. Por extensión, nos ahorra trabajar con funciones anónimas. Lo más notable de async await es que permiten escribir un código asíncrono que se lee más como si fuera un código síncrono tradicional.
Los bloques async
y await
son independientes pero los usamos de manera conjunta. Básicamente sirven para lo siguiente:
- Con async podemos declarar una función asíncrona que básicamente tendrá la habilidad de poder tratar comportamientos asíncronos usando await.
- Con await por su parte conseguimos llamar a una función que devuelve una promesa con una facilidad extra. La ventaja o facilidad de await es que el propio JavaScript esperará a la resolución de la promesa, sin ejecutar las siguientes líneas de código del programa, y una vez tenga la respuesta la devolverá y continuará con el flujo de ejecución de las sentencias.
Por supuesto todo esto se ve bien con ejemplos que vamos a abordar a continuación.
Ejemplos sencillos con async await
Vamos a comenzar con ejemplos sencillos que nos permitan ver cómo funcionan las sentencias async / await.
Creamos una función con async
Comenzamos con async. Esta palabra clave se utiliza antes de declarar una función asincrónica, que devuelve una promesa. En lugar de devolver los valores directamente, una función async
devuelve una promesa que se resolverá con el valor devuelto por la función.
async function devuelvoPromesa() {
return "Resultado devuelto al resolverse";
}
En el código anterior la función devuelvoPromesa()
devuelve una promesa, aunque si la leemos aparentemente está devolviendo un string. Sin embargo es una promesa que se resuelve inmediatamente y entrega el valor: "Resultado devuelto al resolverse
".
Podemos comprobar que verdaderamente esta función devuelve una promesa si escribimos un código como este:
console.log(typeof devuelvoPromesa());
Al ejecutarse el código anterior nos mostrará en la consola el tipo del valor devuelto por la función que es un "object
".
Incluso podríamos hacer uso de instanceof
para averiguar si lo que te devuelve la función es una instancia de la clase Promise
:
console.log(devuelvoPromesa() instanceof Promise );
En la consola aparecerá true
al ejecutarse el código anterior.
En cualquier caso, tal como hemos escrito este código no hemos sacado mucho partido a la función declarada con async
, ya que la gracia de hacerlo es ejecutar el await
para resolver promesas con un estilo de código similar al código síncrono secuencial.
Hacemos uso de await
Ahora vamos a usar la palabra clave await dentro de funciones declaradas como async. La utilidad de esta construcción será para esperar que se resuelva una promesa. Hace que el código asincrónico se parezca y se comporte más como el código común, síncrono, lo que facilita la lectura y comprensión de los programas.
async function devuelvoPromesa() {
let valor = await otraFuncionQueDevuelvePromesa();
console.log(valor); // Este código espera hasta que la promesa se resuelva
return valor;
}
En el código anterior tenemos ahora un uso más útil de async
, ya que hemos colocado un await
dentro. Básicamente lo que hace el await
es pausar la ejecución de devuelvoPromesa()
hasta que otraFuncionQueDevuelvePromesa()
se resuelva. Esto significa que el código después de await
se ejecutará como si fuera código síncrono, es decir, la línea siguiente se ejecutará después que promesa que devuelve otraFuncionQueDevuelvePromesa()
se haya resuelto.
Te mostramos el código de ambas funciones a la vez por si quieres copiar y pegar y así probarlo en tu entorno.
async function devuelvoPromesa() {
let valor = await otraFuncionQueDevuelvePromesa();
console.log(valor); // Este código espera hasta que la promesa se resuelva
return valor;
}
function otraFuncionQueDevuelvePromesa() {
return new Promise( (resolve, reject) =>
setTimeout(() => resolve('valor de respuesta'), 1000)
);
}
Dónde le vamos a sacar partido a async await
Estas utilidades de Javascript son meramente lo que llamamos un "azúcar sintáctico", ya que en el fondo todo funciona igual que con las promesas ya conocidas anteriormente.
Puedes ver este artículo si quieres repasar sobre las promesas en Javascript.
Donde resulta especialmente útil el async
await
es para la realización de operaciones como solicitudes de red o consultas a bases de datos, donde se requiere esperar a que se complete una operación antes de continuar con la ejecución del código siguiente.
Su principal ventaja es reducir la complejidad del código y hacerlo más legible, comparado con el encadenamiento de promesas tradicionales que resolvemos con los bloques then
y catch
.
Vamos a suponer que queremos hacer una función que recibe las tareas que hay que realizar entregadas por un API REST ("ToDos"). Sin async await podríamos llegar a un código como este:
function recibeTodosSinAsyncAwait() {
return new Promise( resolve => {
fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => resolve(json))
})
}
Como se trata de una funcionalidad asíncrona lo máximo que podemos hacer es devolver una promesa que se resolverá cuando se haya recibido respuesta del API REST. En este estilo de codificación somos nosotros mismos los que tenemos que crear un nuevo objeto de la instancia Promise
y resolverla cuando tengamos el dato.
Ahora vamos a ver el código que tendríamos si usamos a async
await
. Como verás se simplifica bastante.
async function recibeTodos() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos');
return await response.json();
}
En este ejemplo en particular hemos utilizado dos veces await, ya que el método fetch() requiere que resolvamos dos promesas hasta obtener el valor del JSON que nos devuelve el API.
Aquí tienes un ejemplo de uso de la función recibeTodos():
recibeTodos().then(todos => console.log(todos));
Cómo tratar casos de promesas rechazadas con async await
La parte más complicada de esta estructura de funcionamiento con async
await
consiste en la resolución de promesas rechazadas, ya que nos obliga a incorporar un bloque try
catch
.
Ahora vamos a ver un código de un acceso a un API REST en el que también vamos a validar que los datos se reciban correctamente, una comprobación importante a la hora de hacer programas robustos.
async function obtenerDatosDeApi() {
try {
// Realizar la solicitud HTTP con fetch y esperar la respuesta
let respuesta = await fetch(https://jsonplaceholder.typicode.com/todos);
// Verificar si la respuesta es exitosa
if (respuesta.ok) {
// Parsear la respuesta como JSON y esperar el resultado
let datos = await respuesta.json();
return datos;
} else {
// Manejar errores de respuesta HTTP (ej. 404, 500)
throw new Error('Error en la solicitud: ' + respuesta.status);
}
} catch (error) {
// Manejar errores de red
console.error('Hubo un problema con la operación fetch: ' + error.message);
}
}
En este ejemplo, la función obtenerDatosDeApi()
es una función asíncrona que usa fetch
para realizar una solicitud HTTP. Es un código similar en el fondo al de antes, solo que realiza comprobaciones de posibles respuestas incorrectas, tanto a nivel de red como a nivel de API.
En la función se utiliza await
para esperar a que se complete la solicitud y para procesar la respuesta. La respuesta se verifica para asegurarse de que la solicitud fue exitosa (respuesta.ok
) y luego se convierte el cuerpo de la respuesta en JSON.
Todo se tiene que incorporar dentro de un bloque try...catch
, pues es la manera que tenemos en Javascript para controlar las excepciones.
Esta es realmente la parte compleja de usar await, que nos obliga a usar excepciones. No obstante, nos ahorra generar las promesas a mano, que ya está bastante bien.
Si ocurre un error durante la solicitud o el procesamiento de la respuesta, se levanta un error, que al final se captura y se trata con el catch
del bloque try
.
Conclusión
Trabajar con la asincronía en el código JavaScript siempre a resultado un poco laborioso. La parte más compleja es organizar el código de manera que sea muy claro y por lo tanto se pueda mantener con facilidad. Es en este sentido donde las mejoras incorporadas en el lenguaje, como el tratamiento de promesas y los bloques async
await
, han venido a ayudar de manera muy notable.
Con este artículo terminamos la serie de entregas dedicada a hablar de asincronía en Javascript. Esperamos que te haya resultado interesante y que hayas aprendido lo suficiente para abordar los problemas complejos que se plantean en las aplicaciones reales. ya sabes que si tienes cualquier duda nos la puedes formular en la sección de faqs.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...