JavaScript: Programación asíncrona

Aprende programación asíncrona en JavaScript con callbacks, promises y async/await para crear aplicaciones eficientes y responsivas.

Aprende JavaScript GRATIS y certifícate

Programación asíncrona en JavaScript

La programación asíncrona representa uno de los paradigmas más importantes en JavaScript moderno. A diferencia de la programación síncrona tradicional, donde cada operación debe completarse antes de continuar con la siguiente, la programación asíncrona permite que el código continúe ejecutándose mientras esperamos que se completen operaciones que pueden tomar tiempo.

El problema de las operaciones bloqueantes

En JavaScript, muchas operaciones requieren tiempo para completarse: peticiones HTTP, lectura de archivos, consultas a bases de datos o temporizadores. Si estas operaciones fueran síncronas, bloquearían completamente la ejecución del programa hasta completarse.

// Ejemplo de operación que toma tiempo
console.log("Inicio");
// Imagina que esta operación tarda 3 segundos
realizarPeticionHTTP();
console.log("Fin"); // Se ejecutaría después de 3 segundos

JavaScript resuelve este problema mediante su modelo de concurrencia basado en un bucle de eventos (event loop) que permite manejar múltiples operaciones de forma no bloqueante.

Callbacks: la base de la asincronía

Los callbacks fueron la primera solución para manejar operaciones asíncronas en JavaScript. Un callback es una función que se pasa como argumento a otra función y se ejecuta cuando la operación asíncrona se completa.

function obtenerDatos(callback) {
    setTimeout(() => {
        const datos = { id: 1, nombre: "Usuario" };
        callback(datos);
    }, 2000);
}

obtenerDatos((resultado) => {
    console.log("Datos recibidos:", resultado);
});

Sin embargo, cuando necesitamos realizar múltiples operaciones asíncronas secuenciales, los callbacks pueden crear el temido "callback hell":

obtenerUsuario(id, (usuario) => {
    obtenerPedidos(usuario.id, (pedidos) => {
        obtenerDetalles(pedidos[0].id, (detalles) => {
            // Código anidado difícil de mantener
        });
    });
});

Promises: una solución más elegante

Las Promises introducen una forma más limpia de manejar operaciones asíncronas. Una Promise representa un valor que puede estar disponible ahora, en el futuro, o nunca.

function obtenerDatos() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const exito = Math.random() > 0.5;
            if (exito) {
                resolve({ id: 1, nombre: "Usuario" });
            } else {
                reject(new Error("Error al obtener datos"));
            }
        }, 2000);
    });
}

Las Promises tienen tres estados posibles:

  • Pending: estado inicial, ni cumplida ni rechazada
  • Fulfilled: operación completada exitosamente
  • Rejected: operación falló

El encadenamiento de Promises permite escribir código asíncrono más legible:

obtenerUsuario(id)
    .then(usuario => obtenerPedidos(usuario.id))
    .then(pedidos => obtenerDetalles(pedidos[0].id))
    .then(detalles => console.log(detalles))
    .catch(error => console.error("Error:", error));

Async/Await: sintaxis moderna

La sintaxis async/await proporciona una forma aún más intuitiva de trabajar con código asíncrono, haciendo que parezca código síncrono:

async function procesarDatos() {
    try {
        const usuario = await obtenerUsuario(id);
        const pedidos = await obtenerPedidos(usuario.id);
        const detalles = await obtenerDetalles(pedidos[0].id);
        console.log(detalles);
    } catch (error) {
        console.error("Error:", error);
    }
}

Una función marcada con async siempre retorna una Promise, y la palabra clave await pausa la ejecución hasta que la Promise se resuelve.

Manejo de múltiples operaciones asíncronas

JavaScript proporciona métodos útiles para manejar múltiples Promises simultáneamente:

Promise.all() ejecuta múltiples Promises en paralelo y espera a que todas se completen:

async function obtenerTodosLosDatos() {
    try {
        const [usuarios, productos, categorias] = await Promise.all([
            obtenerUsuarios(),
            obtenerProductos(),
            obtenerCategorias()
        ]);
        return { usuarios, productos, categorias };
    } catch (error) {
        console.error("Error en alguna operación:", error);
    }
}

Promise.race() retorna el resultado de la primera Promise que se resuelve:

const timeoutPromise = new Promise((_, reject) => 
    setTimeout(() => reject(new Error("Timeout")), 5000)
);

Promise.race([obtenerDatos(), timeoutPromise])
    .then(resultado => console.log(resultado))
    .catch(error => console.error(error));

Patrones comunes en programación asíncrona

El patrón de retry permite reintentar operaciones que pueden fallar:

async function operacionConReintentos(maxIntentos = 3) {
    for (let intento = 1; intento <= maxIntentos; intento++) {
        try {
            return await operacionQuePodriaFallar();
        } catch (error) {
            if (intento === maxIntentos) throw error;
            await esperar(1000 * intento); // Espera progresiva
        }
    }
}

La limitación de concurrencia evita sobrecargar recursos:

async function procesarEnLotes(items, tamañoLote = 5) {
    const resultados = [];
    
    for (let i = 0; i < items.length; i += tamañoLote) {
        const lote = items.slice(i, i + tamañoLote);
        const resultadosLote = await Promise.all(
            lote.map(item => procesarItem(item))
        );
        resultados.push(...resultadosLote);
    }
    
    return resultados;
}

La programación asíncrona en JavaScript es fundamental para crear aplicaciones responsivas y eficientes. Dominar estos conceptos permite escribir código que maneja operaciones complejas sin bloquear la interfaz de usuario, proporcionando una mejor experiencia al usuario final.

Empezar curso de JavaScript

Lecciones de este módulo de JavaScript

Lecciones de programación del módulo Programación asíncrona del curso de JavaScript.

Ejercicios de programación en este módulo de JavaScript

Evalúa tus conocimientos en Programación asíncrona con ejercicios de programación Programación asíncrona de tipo Test, Puzzle, Código y Proyecto con VSCode.