Resumen

Escribir código asíncrono con async/await es cómodo y legible, pero esa comodidad esconde una trampa muy común: ejecutar en secuencia operaciones que podrían correr en paralelo. El resultado es código que funciona, pero que tarda el doble o el triple de lo necesario. Saber distinguir cuándo usar ejecución secuencial y cuándo usar ejecución paralela marca una diferencia directa en el rendimiento de cualquier aplicación.

¿Por qué el await consecutivo hace tu código más lento?

Cuando colocas await en líneas consecutivas sobre operaciones independientes, cada una espera a que termine la anterior antes de comenzar [0:25]. Veamos un ejemplo con tres llamadas a una API de GitHub para cargar usuarios, repositorios y organizaciones:

javascript const users = await fetchUsers(); // 300ms const repos = await fetchRepos(); // 400ms const orgs = await fetchOrganizations(); // 200ms // Total: 900ms

Estas tres operaciones son completamente independientes entre sí: ninguna necesita el resultado de la anterior para ejecutarse. Sin embargo, al usar await línea por línea, se comportan como si estuvieran en fila. El tiempo total es la suma de todas: 900 milisegundos [1:00].

¿Cómo se resuelve con promise.all?

La solución es iniciar todas las promesas al mismo tiempo y usar Promise.all para esperar que todas terminen [1:30]. Promise.all es un combinador de promesas que recibe un array de promesas y devuelve una nueva promesa que se resuelve cuando todas se completan.

javascript const [users, repos, orgs] = await Promise.all([ fetchUsers(), // 300ms fetchRepos(), // 400ms fetchOrganizations() // 200ms ]); // Total: 400ms (la más lenta)

El truco está en no poner await antes de cada llamada individual [1:52]. Primero se crean todas las promesas, lo que inicia las operaciones de inmediato, y luego un solo await sobre Promise.all. El tiempo de espera pasa a ser el de la tarea más lenta: 400 milisegundos en vez de 900.

¿Cómo se aplica esto con una API real de GitHub?

Un error muy sutil pero frecuente es colocar el await antes de tiempo [2:18]. Por ejemplo, al construir un perfil de usuario se pueden lanzar tres peticiones a GitHub y esperar solo lo que tarde la más lenta:

javascript async function getFullProfile(username) { const userRes = await fetch(https://api.github.com/users/${username}); const user = await userRes.json();

const [repos, orgs] = await Promise.all([ fetch(https://api.github.com/users/${username}/repos).then(r => r.json()), fetch(https://api.github.com/users/${username}/orgs).then(r => r.json()) ]);

return { name: user.name, bio: user.bio, followers: user.followers, repos, orgs }; }

Al imprimir en consola se obtiene el nombre, la bio, número de seguidores, los repos y las organizaciones, con una diferencia de tiempo notable frente a la versión secuencial [3:05].

¿Cuándo la ejecución secuencial es obligatoria?

No siempre el paralelo es la respuesta correcta [3:30]. Cuando una operación depende del resultado de la anterior, la secuencia es obligatoria:

javascript async function getRepoStats(username) { const user = await getUser(username); const repos = await getRepos(user.login); const popular = await getMostPopular(repos); const commits = await getCommits(popular); return commits; }

Cada paso necesita la respuesta del anterior, así que no hay forma de paralelizar [3:48].

¿Cómo combinar ambos patrones en aplicaciones reales?

En la práctica, normalmente se combinan el patrón secuencial y el patrón paralelo [4:05]. Primero se ejecuta lo que es dependencia y después se lanza en paralelo todo lo independiente:

javascript async function loadRepoProfile(repoName) { const repo = await fetchRepo(repoName); // obligatorio primero

const [issues, forks, contributors] = await Promise.all([ fetchIssues(repo.id), fetchForks(repo.id), fetchContributors(repo.id) ]);

return { repo, issues, forks, contributors }; }

Los issues, forks y contribuidores no dependen entre sí, así que se ejecutan simultáneamente una vez que el repo está disponible [4:22].

La regla mental es directa:

  • Operaciones independientes: usar Promise.all.
  • Operaciones dependientes: usar await en secuencia.

Identificar cuál es cuál es una habilidad que se construye con práctica y tiene impacto directo en el rendimiento de tus aplicaciones [4:50]. ¿Ya revisaste tu código para detectar awaits innecesariamente secuenciales? Comparte en los comentarios qué mejoras encontraste.