{}const=>[]async()letfn</>var
DesarrolloFundamentosJS

Async/Await: una guía completa para trabajar con código asíncrono

Aprenderemos a trabajar con código asíncrono usando async/await. Aprende a evitar el callback hell, a gestionar correctamente los errores, a optimizar la ejecución paralela de las operaciones y a aplicar patrones avanzados.

К

Kodik

Autor

6 min de lectura

¿Por qué es necesaria la asincronía?

Imagina que tu aplicación realiza una solicitud a una API externa que responde en 2 segundos. Sin asincronía, el programa simplemente se congelará durante este tiempo, bloqueando la ejecución de otras operaciones. El código asíncrono permite que la aplicación continúe funcionando mientras la operación se ejecuta en segundo plano.

🔥 100.000+ estudiantes ya están con nosotros

¿Cansado de leer teoría?
¡Hora de programar!

Kodik — una app donde aprendes a programar con práctica. Mentor IA, lecciones interactivas, proyectos reales.

🤖 IA 24/7
🎓 Certificados
💰 Gratis
🚀 Empezar
Se unieron hoy

De callbacks a promesas y async/await

La evolución del código asíncrono en JavaScript ha pasado por varias etapas:

Funciones de devolución de llamada (enfoque obsoleto):

fetchUser(userId, (error, user) => {
  if (error) {
    console.error(error);
    return;
  }
  fetchPosts(user.id, (error, posts) => {
    if (error) {
      console.error(error);
      return;
    }
    // El infierno de las devoluciones de llamada comienza aquí
  });
});

Promesas (mejora):

fetchUser(userId)
  .then(user => fetchPosts(user.id))
  .then(posts => console.log(posts))
  .catch(error => console.error(error));

Async/Await (enfoque moderno):

async function getUserPosts(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    return posts;
  } catch (error) {
    console.error(error);
  }
}

Fundamentos de async/await

La palabra clave async antes de la función significa que la función siempre devuelve una promesa. La palabra clave await hace que JavaScript espere el resultado de la promesa antes de continuar con la ejecución.

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

Reglas importantes:

  • await solo se puede usar dentro de las funciones async

  • async la función siempre devuelve una promesa

  • Si la función devuelve un valor, se envuelve automáticamente en Promise.resolve()

Tratamiento de errores

Los bloques try-catch son una forma natural de manejar errores en async/await:

async function processOrder(orderId) {
  try {
    const order = await fetchOrder(orderId);
    const payment = await processPayment(order);
    const confirmation = await sendConfirmation(payment);
    return confirmation;
  } catch (error) {
    if (error.code === 'PAYMENT_FAILED') {
      await refundOrder(orderId);
    }
    throw new Error(`Order processing failed: ${error.message}`);
  }
}

Se puede combinar con .catch() para un procesamiento más granular:

async function getData() {
  const data = await fetchData().catch(error => {
    console.error('Fetch failed:', error);
    return getDefaultData(); // Restableciendo datos predeterminados
  });
  return data;
}

Ejecución en paralelo

Uno de los errores más comunes es la ejecución secuencial de operaciones independientes:

// Mal: las operaciones se realizan secuencialmente (6 segundos)
async function loadData() {
  const users = await fetchUsers(); // 3 segundos
  const posts = await fetchPosts(); // 3 segundos
  return { users, posts };
}

Utilice Promise.all() para la ejecución paralela:

// Bien: las operaciones se realizan en paralelo (3 segundos)
async function loadData() {
  const [users, posts] = await Promise.all([
    fetchUsers(),
    fetchPosts()
  ]);
  return { users, posts };
}

Promise.allSettled()

Para los casos en que se necesitan los resultados de todas las operaciones:

async function loadAllData() {
  const results = await Promise.allSettled([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Operation ${index} succeeded:`, result.value);
    } else {
      console.error(`Operation ${index} failed:`, result.reason);
    }
  });
}

Promise.race()

Para operaciones con tiempo de espera:

async function fetchWithTimeout(url, timeout = 5000) {
  const timeoutPromise = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Request timeout')), timeout)
  );
  
  return Promise.race([
    fetch(url),
    timeoutPromise
  ]);
}

Async/await en bucles

Tenga cuidado con los ciclos, ya que pueden crear un comportamiento inesperado:

// Procesamiento secuencial
async function processSequentially(items) {
  for (const item of items) {
    await processItem(item); // Esperando la finalización de cada operación
  }
}

// Procesamiento paralelo
async function processInParallel(items) {
  await Promise.all(items.map(item => processItem(item)));
}

// Procesamiento por lotes
async function processBatches(items, batchSize = 5) {
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    await Promise.all(batch.map(item => processItem(item)));
  }
}

Errores comunes

1. El await olvidado

// Error: la función devolverá una promesa, no datos
async function getData() {
  return fetchData(); // Olvidó await
}

// Correcto
async function getData() {
  return await fetchData();
}

2. Uso de await en forEach

// No funciona: forEach no entiende async
items.forEach(async (item) => {
  await processItem(item);
});

// Usa for...of
for (const item of items) {
  await processItem(item);
}

3. Creación de promesas en el ciclo sin control

// Creará miles de solicitudes simultáneas
const promises = items.map(item => fetchItem(item));
await Promise.all(promises);

// Es mejor controlar el paralelismo

Patrones avanzados

Reintentar con retraso exponencial:

async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetch(url);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      const delay = Math.pow(2, i) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Almacenamiento en caché de los resultados:

const cache = new Map();

async function fetchWithCache(key, fetcher) {
  if (cache.has(key)) {
    return cache.get(key);
  }
  const result = await fetcher();
  cache.set(key, result);
  return result;
}

Debounce para funciones asíncronas:

function asyncDebounce(func, wait) {
  let timeout;
  return function(...args) {
    return new Promise((resolve) => {
      clearTimeout(timeout);
      timeout = setTimeout(async () => {
        resolve(await func.apply(this, args));
      }, wait);
    });
  };
}

Async/await en otros idiomas

El concepto de async/await no solo existe en JavaScript:

Python:

async def fetch_data():
    response = await aiohttp.get('https://api.example.com')
    return await response.json()

C#:

async Task<string> FetchDataAsync() {
    var response = await httpClient.GetAsync("https://api.example.com");
    return await response.Content.ReadAsStringAsync();
}

Rust:

async fn fetch_data() -> Result<String, Error> {
    let response = reqwest::get("https://api.example.com").await?;
    Ok(response.text().await?)
}

Productividad y mejores prácticas

1. Evita el await innecesario

// No óptimo
async function process() {
  const result = await computeValue();
  return result; // Aguardar innecesario
}

// Mejor
async function process() {
  return computeValue();
}

2. Utiliza el paralelismo siempre que sea posible

// Lento
const user = await getUser();
const posts = await getPosts();

// Más rápido
const [user, posts] = await Promise.all([getUser(), getPosts()]);

3. Añade tiempos de espera para solicitudes externas

async function safeFetch(url) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);
  
  try {
    const response = await fetch(url, { signal: controller.signal });
    return response;
  } finally {
    clearTimeout(timeout);
  }
}

Conclusión

Async/await hace que el código asíncrono sea legible y comprensible, eliminando los problemas de callback hell y simplificando el manejo de errores. Puntos clave:

  • Utilice async/await para operaciones secuenciales

  • Utiliza Promise.all() para la ejecución paralela

  • No olvides procesar los errores a través de try-catch

  • Ten cuidado con los ciclos y usa siempre await cuando sea necesario

  • Controle el paralelismo para evitar la sobrecarga del sistema

La práctica y la comprensión de cuándo las operaciones deben realizarse secuencialmente y cuándo en paralelo le ayudarán a escribir un código asíncrono eficaz.

Kodik ofrece cursos estructurados en JavaScript, Python, C, Rust y muchos otros lenguajes, donde puedes aprender programación asíncrona desde los conceptos básicos hasta los patrones avanzados. Las lecciones interactivas con tareas prácticas te ayudarán a consolidar el material con ejemplos reales.

¿Tienes alguna pregunta sobre async/await o te has encontrado con un problema difícil? Únete a nuestro canal de Telegram, donde los desarrolladores experimentados y una comunidad activa siempre están listos para ayudar a resolver cualquier problema, desde sutilezas sintácticas hasta soluciones arquitectónicas. ¡Haz preguntas, comparte experiencias y aprende con la comunidad!

🎯Deja de postergar

¿Te gustó el artículo?
¡Hora de practicar!

En Kodik no solo lees — escribes código de inmediato. Teoría + práctica = habilidades reales.

Práctica instantánea
🧠IA explica código
🏆Certificado

Sin registro • Sin tarjeta