Skip to main content

Async/Await

Async/Await es azúcar sintáctica sobre Promises que hace que el código asíncrono se vea y se comporte más como código síncrono. Es la forma más moderna y legible de manejar asincronismo en JavaScript.

¿Qué es Async/Await?

Async/await son dos palabras clave que trabajan juntas:
  • async: Declara que una función es asíncrona (retorna una Promise)
  • await: Pausa la ejecución hasta que una Promise se resuelva
async function obtenerDatos() {
  const respuesta = await fetch('/api/data');
  const datos = await respuesta.json();
  return datos;
}
await solo puede usarse dentro de funciones async. Intentar usarlo fuera generará un error de sintaxis.

Funciones Async

Una función async siempre retorna una Promise:
async function saludar() {
  return "Hola";
}

// Es equivalente a:
function saludar() {
  return Promise.resolve("Hola");
}

// Uso:
saludar().then(mensaje => console.log(mensaje)); // "Hola"

Declaraciones de Funciones Async

async function miFuncion() {
  return "Hola";
}

Usando Await

await pausa la ejecución de la función async hasta que la Promise se resuelva:
const asyncAwaitBtn = document.getElementById("async-await");

asyncAwaitBtn.addEventListener("click", async function() {
  try {
    console.log("⏳ Cargando...");
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/todos/1"
    );
    const data = await response.json();
    console.log(data);
    console.log("✅ Listo");
  } catch (error) {
    console.log(error);
  }
});
Flujo de ejecución:
  1. Se imprime ”⏳ Cargando…”
  2. await fetch() pausa hasta que la petición termine
  3. await response.json() pausa hasta que el JSON se parsee
  4. Se imprime el objeto de datos
  5. Se imprime ”✅ Listo”
Cada await pausa la función, pero no bloquea el hilo principal de JavaScript. Otras operaciones pueden ejecutarse mientras esperas.

Comparación: Promises vs Async/Await

Con Promises (.then)

function cargarUsuariosThen() {
  usersOutput.textContent = "Cargando...";
  fetch(url)
    .then(function (response) {
      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }
      return response.json();
    })
    .then(function (data) {
      console.log("Users (.then):", data);
      printUsers(data);
    })
    .catch(function (error) {
      console.log(error);
      usersOutput.textContent = error;
    });
}

Con Async/Await

async function cargarUsuariosAsync() {
  usersOutput.textContent = "Cargando...";
  try {
    var response = await fetch(url);
    if (!response.ok) {
      throw new Error("HTTP status " + response.status);
    }
    var data = await response.json();
    console.log("Users (async/await):", data);
    printUsers(data);
  } catch (error) {
    console.log(error);
    usersOutput.textContent = error;
  }
}
Ventajas de async/await:
  • Más fácil de leer - parece código síncrono
  • Menos anidación
  • Manejo de errores más natural con try/catch
  • Mejor para debugging (call stack más claro)

Manejo de Errores con Try/Catch

Usa bloques try/catch para manejar errores en funciones async:
async function obtenerUsuario(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    
    if (!response.ok) {
      throw new Error(`HTTP error ${response.status}`);
    }
    
    const usuario = await response.json();
    console.log('Usuario:', usuario);
    return usuario;
    
  } catch (error) {
    console.error('Error al obtener usuario:', error);
    // Puedes relanzar el error o manejar la recuperación
    throw error;
  }
}

Try/Catch vs .catch()

async function ejemplo() {
  try {
    const resultado = await operacionAsincrona();
    return resultado;
  } catch (error) {
    console.error(error);
  }
}
Ventajas:
  • Sintaxis familiar
  • Maneja errores síncronos y asíncronos
  • Mejor para múltiples operaciones await

Ejecutar Promises en Paralelo

❌ Incorrecto: Secuencial (Lento)

async function obtenerTodo() {
  const usuarios = await fetch('/api/users');     // Espera 1 segundo
  const productos = await fetch('/api/products'); // Espera otro segundo
  const ordenes = await fetch('/api/orders');     // Espera otro segundo
  
  // Total: 3 segundos
}

✅ Correcto: Paralelo (Rápido)

async function obtenerTodo() {
  // Iniciar todas las peticiones al mismo tiempo
  const promesaUsuarios = fetch('/api/users');
  const promesaProductos = fetch('/api/products');
  const promesaOrdenes = fetch('/api/orders');
  
  // Esperar a que todas terminen
  const usuarios = await promesaUsuarios;
  const productos = await promesaProductos;
  const ordenes = await promesaOrdenes;
  
  // Total: ~1 segundo (la más lenta)
}

Usando Promise.all() con Async/Await

async function obtenerTodo() {
  try {
    const [usuarios, productos, ordenes] = await Promise.all([
      fetch('/api/users'),
      fetch('/api/products'),
      fetch('/api/orders')
    ]);
    
    console.log('Todo cargado en paralelo');
    return { usuarios, productos, ordenes };
    
  } catch (error) {
    console.error('Al menos una petición falló:', error);
  }
}
Si las operaciones no dependen entre sí, ejecútalas en paralelo. De lo contrario, estarás esperando innecesariamente.

Await en Ciclos

For Loop

async function procesarUsuarios(ids) {
  for (const id of ids) {
    const usuario = await fetch(`/api/users/${id}`);
    console.log('Usuario cargado:', usuario);
  }
  // Los usuarios se procesan uno por uno (secuencial)
}

Paralelo con map()

async function procesarUsuarios(ids) {
  const promesas = ids.map(id => fetch(`/api/users/${id}`));
  const usuarios = await Promise.all(promesas);
  console.log('Todos los usuarios cargados:', usuarios);
  // Los usuarios se cargan todos al mismo tiempo (paralelo)
}

Ejemplo Completo: Carga de Datos de Usuario

const btnUsersThen = document.getElementById("users-then");
const btnUsersAsync = document.getElementById("users-async");
const usersOutput = document.getElementById("users-output");
const url = "https://jsonplaceholder.typicode.com/users";

function printUsers(usersList) {
  var users = [];
  for (var i = 0; i < usersList.length; i++) {
    var user = usersList[i];
    var userDataFormatted = user.id + " - " + user.name + " (" + user.email + ")";
    users.push(userDataFormatted);
  }
  usersOutput.textContent = users.join("\n");
}

async function cargarUsuariosAsync() {
  usersOutput.textContent = "Cargando...";
  try {
    var response = await fetch(url);
    if (!response.ok) {
      throw new Error("HTTP status " + response.status);
    }
    var data = await response.json();
    console.log("Users (async/await):", data);
    printUsers(data);
  } catch (error) {
    console.log(error);
    usersOutput.textContent = error;
  }
}

btnUsersAsync.addEventListener("click", cargarUsuariosAsync);

Async/Await con .finally

Puedes usar .finally() con async/await:
async function cargarDatos() {
  try {
    mostrarSpinner();
    const datos = await fetch('/api/data');
    procesarDatos(datos);
  } catch (error) {
    mostrarError(error);
  } finally {
    ocultarSpinner(); // Se ejecuta siempre
  }
}
O mezclarlo con Promises:
async function cargarDatos() {
  mostrarSpinner();
  
  const datos = await fetch('/api/data')
    .catch(error => {
      mostrarError(error);
      return null;
    })
    .finally(() => {
      ocultarSpinner();
    });
  
  if (datos) {
    procesarDatos(datos);
  }
}

Top-Level Await

En módulos ES6 modernos, puedes usar await en el nivel superior (fuera de funciones async):
// En un módulo ES6
const datos = await fetch('/api/data');
console.log(datos);
Top-level await solo funciona en módulos ES6 (<script type="module"> o archivos .mjs en Node.js).

Buenas Prácticas

1

Siempre usa try/catch

// ❌ Mal: Sin manejo de errores
async function obtenerDatos() {
  const data = await fetch('/api/data');
  return data;
}

// ✅ Bien: Con try/catch
async function obtenerDatos() {
  try {
    const data = await fetch('/api/data');
    return data;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}
2

Ejecuta en paralelo cuando sea posible

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

// ✅ Rápido: Paralelo
const [user, posts] = await Promise.all([
  getUser(),
  getPosts()
]);
3

No bloquees innecesariamente

// ❌ Mal: await innecesario
async function sumar(a, b) {
  return await (a + b); // await es innecesario aquí
}

// ✅ Bien: Sin await innecesario
async function sumar(a, b) {
  return a + b;
}

Cuándo Usar Async/Await

  • Código nuevo que escribas
  • Múltiples operaciones asíncronas en secuencia
  • Cuando necesitas lógica compleja con await
  • Para mejor legibilidad del código
  • Cuando necesitas usar try/catch
  • Una sola operación asíncrona simple
  • Cuando necesitas encadenar transformaciones
  • APIs que esperan Promises como retorno
  • Cuando Promise.all/race/etc. son más claros

Próximos Pasos

Fetch API

Usa async/await para hacer peticiones HTTP

Ejemplos Asíncronos

Ve ejemplos completos de código asíncrono

Build docs developers (and LLMs) love