8 ‐ ¿Qué hacen async y await por nosotros? - Jabes-Gonzalez/Checkpoint-8 GitHub Wiki
Las palabras clave async y await revolucionaron el manejo de la asincronía en JavaScript, permitiendo escribir código asíncrono que se lee y se estructura como si fuera síncrono. Esto mejora la legibilidad, facilita el manejo de errores y simplifica la programación de operaciones complejas y dependientes entre sí.
¿Qué son async y await?
async: Convierte automáticamente una función en una función asíncrona que siempre devuelve una promesa.
await: Solo puede usarse dentro de funciones async. Pausa la ejecución de la función hasta que la promesa se resuelva o rechace, devolviendo su resultado o lanzando una excepción en caso de error.
Sintaxis básica:
async function obtenerDatos() {
const respuesta = await fetch('https://api.example.com/datos');
const datos = await respuesta.json();
return datos;
}
El flujo de la función se detiene en cada await
hasta que la promesa se resuelve, haciendo el código más fácil de seguir y depurar.
¿Qué hacen por nosotros async y await?
- Hacen el código asíncrono más legible y lineal: Permiten escribir operaciones asíncronas con una sintaxis similar al código síncrono tradicional, eliminando la necesidad de encadenar múltiples .then() o callbacks anidados.
Ejemplo comparativo:
Con promesas:
fetch('https://api.example.com/datos')
.then(res => res.json())
.then(datos => console.log(datos))
.catch(error => console.error(error));
Con async/await:
async function mostrarDatos() {
try {
const res = await fetch('https://api.example.com/datos');
const datos = await res.json();
console.log(datos);
} catch (error) {
console.error(error);
}
}
El segundo ejemplo es más fácil de leer y mantener, especialmente en flujos complejos.
- Facilitan el manejo de errores:
Permiten usar bloques
try
/catch
para capturar excepciones, en vez de depender solo de.catch()
de las promesas.
async function cargarUsuario() {
try {
const res = await fetch('/api/usuario');
if (!res.ok) throw new Error('No se pudo cargar el usuario');
const usuario = await res.json();
return usuario;
} catch (error) {
console.error('Error:', error);
return null;
}
}
El manejo de errores es directo y centralizado, como en código síncrono.
- Permiten pausar y reanudar la ejecución de funciones:
await
suspende la ejecución de la función async hasta que la promesa se resuelve, sin bloquear el hilo principal ni consumir recursos de CPU. - Mejoran la depuración y el mantenimiento: El código con async/await es más fácil de depurar, ya que los errores se propagan como excepciones normales y el stack trace es más claro.
- Permiten estructurar operaciones secuenciales y paralelas: Puedes esperar operaciones una tras otra (secuencial) o lanzar varias en paralelo y esperar sus resultados con Promise.all() y await.
Ejemplo:
async function cargarTodo() {
const [usuario, posts] = await Promise.all([
fetch('/api/usuario').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
console.log(usuario, posts);
}
Ejemplos Prácticos y Avanzados
- Esperar múltiples operaciones secuenciales
async function procesoSecuencial() {
const paso1 = await tareaAsincrona1();
const paso2 = await tareaAsincrona2(paso1);
return await tareaAsincrona3(paso2);
}
- Manejo de errores en animaciones
const animarElemento = async () => {
try {
await elemento.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 });
// Continuar después de la animación
} catch (error) {
console.error("Error en la animación", error);
}
};
Permite esperar a que termine una animación antes de continuar con el flujo del programa.
Buenas Prácticas y Advertencias
- Solo puedes usar
await
dentro de funcionesasync
. - Una función
async
siempre devuelve una promesa, aunque no usesawait
en su interior. await
solo pausa la función donde se encuentra, no el hilo principal ni otras funciones.- Para operaciones concurrentes, usa
Promise.all()
conawait
para mayor eficiencia.
Ventajas Resumidas
- Código asíncrono más legible y mantenible.
- Manejo de errores más simple y centralizado.
- Flujo estructurado y lineal, similar al código síncrono.
- Mejor depuración y stack trace más claros.
- Facilita la escritura de operaciones complejas y dependientes.
Casos de Uso Comunes de async/await
Llamadas a APIs en Secuencia y en Paralelo
- Secuencial: Espera a que una llamada termine antes de iniciar la siguiente.
async function cargarDatosSecuencial() {
const usuario = await fetch('/api/usuario').then(r => r.json());
const posts = await fetch(`/api/posts?user=${usuario.id}`).then(r => r.json());
return { usuario, posts };
}
- Paralelo: Lanza varias operaciones a la vez y espera a que todas terminen.
async function cargarDatosParalelo() {
const [usuario, posts] = await Promise.all([
fetch('/api/usuario').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { usuario, posts };
}
Esto mejora el rendimiento, ya que las operaciones no dependen entre sí y se ejecutan simultáneamente.
Acceso a Bases de Datos y Procesamiento de Archivos
- Acceso a bases de datos:
async function obtenerUsuario(id) {
const usuario = await db.getUserById(id);
return usuario;
}
- Procesamiento de archivos (Node.js):
const fs = require('fs/promises');
async function leerArchivo(ruta) {
try {
const contenido = await fs.readFile(ruta, 'utf-8');
return contenido;
} catch (error) {
return null;
}
}
Permite manejar operaciones de entrada/salida sin bloquear el hilo principal.
Buenas Prácticas con async/await
Estructura y Legibilidad
- Organiza el código en funciones pequeñas y descriptivas.
- Nombra las funciones de manera clara sobre lo que hacen.
- Evita anidar excesivamente funciones async; usa funciones auxiliares para mantener el flujo limpio.
Manejo de Errores Centralizado
- Usa bloques try/catch para capturar errores en operaciones asíncronas.
- Proporciona mensajes claros y detallados en los errores.
- Utiliza
finally
para limpiar recursos o actualizar el estado, sin importar si hubo éxito o error.
async function ejemplo() {
try {
const res = await fetch('/api/datos');
if (!res.ok) throw new Error('Error en la solicitud');
return await res.json();
} catch (error) {
return { error: error.message };
} finally {
console.log('Operación finalizada');
}
}
Uso Razonable de Promise.all
- Usa Promise.all para ejecutar tareas independientes en paralelo y esperar a que todas finalicen.
- Maneja los errores de forma adecuada, ya que si una promesa falla,
Promise.all
rechaza inmediatamente.
Evita await en Bucles
- Usar await en bucles puede hacer que las operaciones se ejecuten una tras otra, lo que es ineficiente.
- Prefiere mapear las operaciones a un array de promesas y luego usar
Promise.all
para ejecutarlas en paralelo.
// Ineficiente
for (const url of urls) {
await fetch(url);
}
// Mejor
await Promise.all(urls.map(url => fetch(url)));
Detalles Técnicos y Advertencias
-
await
suspende la ejecución de la funciónasync
hasta que la promesa se resuelve, pero no bloquea el hilo principal: el motor de JavaScript puede seguir ejecutando otras tareas mientras tanto. -
Si intentas usar
await
fuera de una funciónasync
, obtendrás un error de sintaxis. -
Una función declarada como
async
siempre devuelve una promesa, incluso si retorna un valor simple. -
Los errores lanzados dentro de una función
async
se convierten en promesas rechazadas y pueden ser capturados con.catch()
otry
/catch
.
Mejoras de Rendimiento y Mantenibilidad
- El uso de async/await permite maximizar el uso de recursos del sistema, liberando el hilo de ejecución durante operaciones largas y permitiendo que otras tareas sigan ejecutándose.
- Facilita la depuración y el mantenimiento, ya que el flujo es más lineal y los errores se propagan como excepciones normales.