Command Palette

Search for a command to run...

Sintaxis async / await: Código Asíncrono Legible en JavaScript

Aprende a usar async y await para escribir código asíncrono que parece síncrono. Domina esta sintaxis moderna que hace el manejo de promesas más intuitivo y fácil de mantener.

Lectura: 12 min
Nivel: Intermedio

TL;DR - Resumen rápido

  • async declara una función como asíncrona y siempre devuelve una promesa
  • await pausa la ejecución hasta que la promesa se resuelva, sin bloquear el hilo
  • Las funciones async retornan promesas automáticamente, incluso con valores primitivos
  • Usa Promise.all() con await para ejecutar operaciones independientes en paralelo
  • Top-level await (ES2022) permite usar await en el nivel superior de módulos ES6

Introducción a async / await

async y await son palabras clave introducidas en ES2017 (ES8) que simplifican enormemente el trabajo con promesas en JavaScript. Antes de async/await, tenías que usar then() y catch() para manejar operaciones asíncronas, lo que podía resultar en código anidado y difícil de leer. Con async/await, puedes escribir código asíncrono que parece y se lee como código síncrono.

La palabra clave async se usa para declarar funciones asíncronas, mientras que await se usa para esperar el resultado de una promesa. Esta combinación hace que el código sea más legible, más fácil de mantener y menos propenso a errores. Es importante entender que async/await no reemplaza a las promesas, sino que es una forma más conveniente de trabajar con ellas.

Azúcar sintáctico

async/await es "azúcar sintáctico" sobre las promesas, lo que significa que es una forma más conveniente de escribir código que usa promesas por debajo. Por debajo, async/await se convierte en promesas y then()/catch(), por lo que puedes usarlas juntas sin problemas.

Sintaxis Básica

La sintaxis de async/await es simple y directa. La palabra clave async se coloca antes de la palabra clave function para declarar una función asíncrona, mientras que await se coloca antes de una expresión que devuelve una promesa para esperar su resultado.

async-await-basico.js
Loading code...

En este ejemplo, la función `obtenerUsuario()` está marcada con async, lo que significa que siempre devuelve una promesa. Dentro de la función, usamos await para esperar el resultado de la promesa `fetchUsuario()`. El código parece síncrono, pero en realidad se ejecuta de manera asíncrona sin bloquear el hilo principal.

Funciones async Retornan Promesas

Una característica fundamental de las funciones async es que SIEMPRE retornan una promesa, incluso si retornas un valor primitivo. JavaScript automáticamente envuelve el valor de retorno en Promise.resolve().

async-return-values.js
Loading code...

Este ejemplo demuestra que las funciones async siempre retornan promesas. Cuando retornas un valor primitivo (como un número o string), JavaScript automáticamente lo envuelve en Promise.resolve(). Esto significa que puedes usar then() o await para obtener el valor retornado.

await solo en funciones async (y módulos ES6)

Solo puedes usar await dentro de funciones marcadas con async o en el nivel superior de módulos ES6 (top-level await desde ES2022). Si intentas usar await en otros contextos, obtendrás un error de sintaxis.

Cómo Funciona async / await

Cuando usas await en una promesa, JavaScript pausa la ejecución de la función async hasta que la promesa se resuelva o se rechace. Durante este tiempo, el hilo principal no se bloquea y puede ejecutar otras tareas. Una vez que la promesa se completa, la ejecución de la función async continúa con el resultado de la promesa.

Ejecución Paralela vs Secuencial

Un error común con async/await es ejecutar operaciones secuencialmente cuando podrían ejecutarse en paralelo. Esto afecta significativamente el rendimiento de tu aplicación.

parallel-vs-sequential.js
Loading code...

Este ejemplo muestra claramente la diferencia de rendimiento. La versión secuencial tarda 3 segundos (1s + 1s + 1s), mientras que la versión paralela con Promise.all() tarda solo 1 segundo porque las tres promesas se ejecutan simultáneamente. Siempre usa Promise.all() cuando las operaciones son independientes.

Manejo de Errores con try/catch

Puedes usar bloques try/catch para capturar errores en operaciones await, lo que hace el manejo de errores más intuitivo y similar al código síncrono.

async-await-try-catch.js
Loading code...

En este ejemplo, usamos try/catch para capturar errores en la operación await. Si la promesa se rechaza, el error se captura en el bloque catch y podemos manejarlo de manera apropiada. Este patrón es mucho más limpio y legible que usar then() y catch(), y hace el manejo de errores más intuitivo, especialmente para desarrolladores que vienen de lenguajes síncronos.

Ventajas sobre Promesas

async/await ofrece varias ventajas significativas sobre el uso directo de promesas con then() y catch(). Estas ventajas hacen que el código sea más mantenible, más fácil de leer y menos propenso a errores.

  • <strong>Legibilidad:</strong> El código parece síncrono y es más fácil de entender.
  • <strong>Menos anidación:</strong> Evita el callback hell y el anidamiento excesivo.
  • <strong>Manejo de errores:</strong> try/catch es más familiar que then()/catch().
  • <strong>Depuración:</strong> Los stack traces son más claros y fáciles de seguir.
  • <strong>Variables:</strong> Puedes usar variables normales en lugar de encadenamiento.
comparacion-then-async.js
Loading code...

Este ejemplo muestra la diferencia clara entre usar then() y async/await. La versión con then() está anidada y más difícil de leer, mientras que la versión con async/await es lineal y clara. Aunque ambas hacen lo mismo, async/await hace el código mucho más mantenible y fácil de entender.

No es reemplazo, es complemento

async/await no reemplaza a las promesas, sino que es una forma más conveniente de trabajar con ellas. Puedes mezclar async/await con promesas tradicionales, usar Promise.all(), Promise.race() y otros métodos de Promise junto con await sin problemas.

Top-level await (ES2022)

Desde ES2022, JavaScript soporta top-level await en módulos ES6. Esto significa que puedes usar await directamente en el nivel superior de un módulo sin necesidad de envolverlo en una función async. Esto es especialmente útil para inicialización de módulos y carga de configuración.

top-level-await.js
Loading code...

Top-level await te permite escribir código de inicialización más limpio y directo. Sin embargo, ten en cuenta que un módulo con top-level await bloqueará la ejecución de módulos que lo importen hasta que el await se complete. Esto puede afectar el tiempo de carga inicial de tu aplicación si se usa en exceso.

Requisitos para top-level await

Top-level await solo funciona en módulos ES6 (archivos con type="module" en Node.js o usando import/export en navegadores). No funciona en scripts comunes o CommonJS. Además, puede afectar el tiempo de carga si abusas de él.

Errores Comunes

Al trabajar con async/await, hay varios errores que los desarrolladores cometen frecuentemente. Conocer estos errores te ayudará a evitarlos y escribir código más robusto.

  • <strong>await fuera de async:</strong> Solo puedes usar await dentro de funciones async o en top-level de módulos ES6.
  • <strong>No manejar errores:</strong> Olvidar try/catch puede causar unhandled rejections.
  • <strong>Ejecución secuencial innecesaria:</strong> No usar Promise.all() cuando las operaciones son independientes.
  • <strong>Olvidar return:</strong> Las funciones async deben retornar valores o promesas explícitamente.
  • <strong>Mezclar callbacks con async/await:</strong> Esto hace el código más difícil de mantener.

Advertencia: Errores no capturados

Si una promesa se rechaza en un await y no hay try/catch que la capture, el error se convierte en un "unhandled rejection". Esto puede causar advertencias en la consola y comportamiento impredecible en tu aplicación. Siempre usa try/catch alrededor de operaciones await que pueden fallar.

Resumen: async / await

Conceptos principales:

  • async declara una función como asíncrona y siempre devuelve una promesa
  • await pausa la ejecución hasta que la promesa se resuelva, sin bloquear el hilo
  • Las funciones async retornan promesas automáticamente, envolviendo valores primitivos
  • try/catch funciona con await para capturar errores de manera intuitiva
  • Top-level await (ES2022) permite usar await en módulos ES6 sin función async
  • async/await es azúcar sintáctico sobre promesas, no una tecnología nueva

Mejores prácticas:

  • Usa Promise.all() con await para ejecutar operaciones independientes en paralelo
  • Siempre incluye try/catch alrededor de operaciones await que pueden fallar
  • Evita await secuencial innecesario - afecta significativamente el rendimiento
  • Retorna valores explícitamente desde funciones async para mayor claridad
  • Usa top-level await solo para inicialización crítica, no para todo
  • Mantén las funciones async pequeñas y enfocadas en una sola responsabilidad