En este tutorial explicaremos cómo crear funciones asíncronas usando la sintaxis async
y await
, que son una evolución de las promesas de JavaScript.
Contenidos
Introducción
El código asíncrono es aquel que sale del hilo principal de ejecución de JavaScript, ejecutándose en paralelo. Si bien JavaScript es un lenguaje síncrono por naturaleza, existen ciertas funcionalidades en los entornos de ejecución de JavaScript o en los navegadores que permiten la ejecución de código asíncrono.
Inicialmente, el código asíncrono en JavaScript se ejecutaba únicamente gracias al uso de callbacks. Sin embargo, en la versión ES2015 de JavaScript se introdujeron las promesas para evitar ciertos problemas que hacían que el uso de callbacks se complicase bastante cuando se anidaban. Pero con el objetivo de facilitar todavía más su uso, se introdujo la sintaxis async
/await
en la versión ES2017 de JavaScript.
Esta sintaxis permite crear funciones que usan tanto promesas como generadores, siendo una abstracción de alto nivel de las promesas de JavaScript. La única diferencia es que las funciones que usan async
/await
, gracias al uso de generadores, pueden pausarse a sí mismas y continuar su ejecución en otro momento.
Qué es al sintaxis async
/away
Cuando las promesas aparecieron en la versión ES2015 de JavaScript, resolvían los problemas que se daban con el uso de callbacks a la hora de gestionar el código asíncrono. El mayor era el callback hell, que se daba cuando se anidaban varios callbacks que ejecutaban código asíncrono, ya que el código se volvía complicado y poco legible.
Sin embargo, las promesas eran mejorables, ya que también agregaban su propia complejidad y una sintaxis difícil de entender. Por ello, aparecieron las funciones asíncronas, que usan la sintaxis async
/await
, permitiendo seguir un estilo de programación síncrono aún creando funciones asíncronas.
Cómo crear una función asíncrona con async
Para crear una función asíncrona con async
, bastará con que antepongas el término async
antes de la definición de la función:
async function obtenerDatos() {
return 'hola!';
}
Esa función no gestiona código asíncrono, pero funciona de un modo diferente a las funciones tradicionales, ya que cuando la ejecutes podrás comprobar que devuelve una promesa. De hecho, si inspeccionas el código, podrás comprobar que la promesa que es devuelta incluye las propiedades PromiseStatus
y PromiseValue
. Para comprobarlo, ejecuta la función obtenerDatos
:
console.log(obtenerDatos());
Podrás ver este resultado por la consola:
__proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: Object
Esto significa que podrás gestionar cualquier función que use async
como una promesa, pudiendo utilizar then
, catch
o finally
al igual que lo harías en una promesa:
obtenerDatos().then(response => console.log(response));
Al ejecutar el código anterior deberías ver el siguiente resultado por la consola:
hola!
Cuando usas async
con una función, se devolverá una promesa aunque no la devuelvas explícitamente, ya que el entorno de ejecución de JavaScript se encargará de que así sea. Por ello, las dos funciones que ves a continuación son equivalentes:
const funcionA = async () => {
return 'Hola!';
}
funcionA().then((resultado) => console.log(resultado));
const functionB = async () => {
return Promise.resolve('Hola!');
}
funcionB().then((resultado) => console.log(resultado));
Cómo usar await
en una función asíncrona
Las funciones asíncronas permiten ejecutar promesas en su interior mediante el operador await
, que esperará a que la promesa revuelva un valor antes de continuar la ejecución del código.
Vamos a poner como ejemplo la siguiente promesa:
const tareaAsincrona = () => {
return new Promise(resolve => {
setTimeout(() => resolve('Tarea completada'), 3000);
})
}
Mediante el uso de await
en una función asíncrona, podremos ejecutar la promesa y obtener su resultado, pausando la ejecución de la función hasta que la promesa devuelva un resultado.
async function gestionarTareas() {
const response = await tareaAsincrona();
console.log(response);
}
Tal y como has visto, puedes usar await
en las funciones asíncronas declaradas con async
. Del mismo modo, también puedes usar await
con la función fetch
para realizar una petición a una API.
En el siguiente ejemplo obtenemos los datos de un usuario desde GitHub usando la API Fetch en una función asíncrona:
async function obtenerUsuario() {
const response = await fetch('https://api.github.com/users/edulazaro');
const data = await response.json();
console.log(data);
}
obtenerUsuario();
El operador await
que anteponemos a la función fetch
en el interior de la función obtenerUsuario
evitará que la línea siguiente se ejecute hasta que no obtengamos un valor de vuelta. De este modo nos aseguramos de que la constante data
no esté vacía. Como ves, no es necesario usar then
, como sí hacemos con las promesas, para obtener el resultado.
Este será el resultado que se muestre por la consola:
blog: "https://www.neoguias.com" twitter_username: "neeonez" type: "User" url: "https://api.github.com/users/edulazaro" updated_at: "2021-01-14T11:22:45Z" ....
await
en el interior de una función definida con async
. Sin embargo, es posible usar await
fuera de estas funciones en las últimas versiones de los navegadores más utilizados y del Node.js, evitando la necesidad de crear una función con async
.await
y el uso de then()
con una promesa. El uso de await
suspende la ejecución de la función actual hasta que el código de contexto superior se termine de ejecutar, mientras que usando then()
, el cierpo de la promsa se seguirá ejecutando. Vamos a verlo con un ejemplo:
const miFuncion= () => Promise.resolve('Tres');
async function miFuncion() {
console.log('Dos');
const res = await getC();
console.log(res);
}
console.log('Uno')
miFuncion();
console.log('Cuatro');
En el ejemplo anterior, primero se mostrará Uno
por la consola, luego Dos
, seguidamente Cuatro
y finalmente Tres
. Esto es debido a que, cuando se ejecuta la función miFuncion(), se obtiene el resultado de getC()
, pero antes de ser asignado a la constante sres, dado que hemos usado await
, se finalizará primero todo el código de los contextos superiores. Por ello, antes de que se muestre Tres por pantalla, se ejecutará primero el console.log(‘Cuatro’) y el resto del código de dicho contexto, de haberlo. Al finalizar, se retoma la ejecución de la función miFuncion(), asignándose Tres a la constante res y ejecutándose finalmente el console.log(res).
Si hubiésemos usando un await con la función miFuncion(), de estar la llamada en el interior de una función asíncrona, el resultado sería otro. Primero se mostraría Uno
por la consola, luego Dos
, seguidamente Tres
y finalmente Cuatro
.
Cómo gestionar errores con catch
Dado que estamos trabajando con funciones asíncronas, también podrás gestionar los errores de la misma. Cuando usas una promesa, tienes disponible el método catch
, usado junto al método then
, que te permite capturar los posible errores que ocurran durante su ejecución. Sin embargo, en este caso usaremos una sentencia try
/catch
para obtener el mismo efecto.
Vamos a gestionar las posibles excepciones del ejemplo del apartado anterior, en el que obteníamos un usuario de GitHub usando fetch
:
async function obtenerUsuario() {
try{
// Si todo ha ido bien
const response = await fetch('https://api.github.com/users/edulazaro');
const data = await response.json(); console.log(data);
} catch(error) {
// Ha ocurrido algún error
console.log(error);
}
}
obtenerUsuario();
El código anterior saltará hasta la sentencia catch
en caso de que reciba un error, mostrándolo por la consola.
Por qué debes usar async
/await
Tal y como has visto, el código es mucho más fácil de entender cuando usas async
/await
en comparación con el uso de promesas. Sin embargo, este fenómeno se acentúa más cuando en una promesa usarías varias sentencias then
en cadena, siendo cuando más percibas sus ventajas.
Vamos a ver un ejemplo algo más complejo en el que obtendremos un archivo JSON para luego realizar varias tareas con él. Primero usaremos una promesa y luego async
/await
.
En el código que ves a continuación obtenemos un archivo JSON. Luego obtenemos el primer elemento del mismo y seguidamente realizamos una petición a una API para obtener un recurso, para finalmente transformar el resultado a formato JSON:
const obtenerPrimeraFila = () => {
return fetch('/filas.json')
.then(res => res.json())
.then(filas => filas[0])
.then(fila => fetch(`/filas/${fila.id}`))
.then(res => res.json());
}
obtenerPrimeraFila();
Si ahora usamos async
/await
, comprobarás que el código es más sencillo:
const obtenerPrimeraFila = async () => {
const res = await fetch('/filas.json');
const filas = await res.json();
const fila = filas[0];
const resFila = await fetch(`/filas/${fila.id}`);
const datos = await resFila.json();
return datos;
}
obtenerPrimeraFila();
Otra ventaja del usar async
/await
es que la depuración del código será muy sencilla en comparación con el uso de promesas. Esto es debido a que por defecto, el depurador no parará su ejecución con el código asíncrono, pero sí lo hará cuando uses async
/await
, ya que se ejecuta como el código síncrono.
Cómo encadenar funciones asíncronas
Las funciones asíncronas también pueden encadenarse al igual que las promesas, y además con una sintaxis más fácil de entender, ya que bastará con usar el operador +
. A continuación puedes ver un ejemplo:
const comerFruta = () => {
return new Promise(resolve => {
setTimeout(() => resolve('Me gusta comer fruta'), 1000);
});
}
const comerFrutaVerdura = async () => {
const fruta = await comerFruta();
return fruta + ' y verdura';
}
comerFrutaVerdura().then(res => {
console.log(res);
});
El código anterior mostrará el siguiente mensaje por la consola cuando ejecutes la función comerFruta()
:
comerFruta(); // Me gusta comer fruta y verdura
Limitaciones del uso de async
/await
Actualmente resulta mucho más frecuente el uso de async
/await
que el uso de promesas. Sin embargo, las promesas disponen de funcionalidades adicionales que no podrás conseguir con async
/await
. Un ejemplo de ello es la combinación de varias promesas mediante Promise.all()
o el método Promise.race()
.
En el fondo, cuando usas async
/await
estás usando promesas junto con generadores, que son capaces de pausar la ejecución del código, haciendo que el código sea más flexible.
Alternativas a async
/await
En la actualidad, lo recomendable es que uses async
/await
siempre que sea posible en lugar de usar promesas directamente, ya que el código será más legible y fácil de entender por otros desarrolladores.
Sin embargo, en ocasiones puede que sea necesario usar callbacks o promesas debido a las limitaciones de async
/await
. Si quieres obtener más información acerca de su uso, consulta los siguientes tutoriales:
- Promesas: Promesas en JavaScript
- Callbacks: Funciones callback en JavaScript
Y esto ha sido todo.
en el ejemplo de
[
const miFuncion= () => Promise.resolve(‘Tres’);
async function miFuncion() {
console.log(‘Dos’);
const res = await getC();
console.log(res);
}
console.log(‘Uno’)
miFuncion();
console.log(‘Cuatro’);
]
declaras dos veces la funcion ‘miFuncion’ , no se si lo has escrito mal o yo no lo comprendo, no se puede ejecutar ese codigo
Por fin me he enterado. Mil gracias