0%
JavaScript moderno: Promises y Async/Await

Promises y Async

Programación asíncrona moderna

10-15 min

JavaScript moderno: Promises y Async/Await

La programación asíncrona está en el corazón del JavaScript moderno. Las Promises y async/await son herramientas esenciales para gestionar las operaciones asíncronas de forma elegante y mantenible.

Las Promises

Una Promise es un objeto que representa la finalización (o el fallo) eventual de una operación asíncrona.

Creación de una Promise

const maPromesse = new Promise((resolve, reject) => {
    // Opération asynchrone
    setTimeout(() => {
        const succes = true;
        if (succes) {
            resolve("Opération réussie !");
        } else {
            reject(new Error("L'opération a échoué"));
        }
    }, 1000);
});

// Utilisation
maPromesse
    .then(resultat => console.log(resultat))
    .catch(erreur => console.error(erreur));

Estados de una Promise

Una Promise puede encontrarse en uno de los tres estados siguientes:

  • Pending: estado inicial, ni cumplida ni rechazada
  • Fulfilled: operación completada con éxito
  • Rejected: operación fallida
const promessePending = new Promise(() => {});
console.log(promessePending); // Promise { <pending> }

const promesseFulfilled = Promise.resolve("Succès");
console.log(promesseFulfilled); // Promise { <fulfilled>: "Succès" }

const promesseRejected = Promise.reject("Erreur");
console.log(promesseRejected); // Promise { <rejected>: "Erreur" }

Encadenamiento de Promises

Las Promises pueden encadenarse para ejecutar operaciones secuenciales:

function obtenirUtilisateur(id) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ id, nom: "Alice" });
        }, 1000);
    });
}

function obtenirCommandes(utilisateur) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({
                utilisateur,
                commandes: [
                    { id: 1, produit: "Livre" },
                    { id: 2, produit: "Ordinateur" }
                ]
            });
        }, 1000);
    });
}

// Chaînage
obtenirUtilisateur(1)
    .then(utilisateur => obtenirCommandes(utilisateur))
    .then(resultat => console.log(resultat))
    .catch(erreur => console.error(erreur));

Métodos útiles con las Promises

Promise.all()

Espera a que todas las promises se resuelvan:

const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve(42), 1000));
const promise3 = fetch('https://api.example.com/data');

Promise.all([promise1, promise2, promise3])
    .then(resultats => {
        console.log(resultats); // [3, 42, Response]
    })
    .catch(erreur => {
        console.error("Une des promises a échoué:", erreur);
    });

Promise.race()

Devuelve la primera promise resuelta o rechazada:

const promise1 = new Promise(resolve => setTimeout(() => resolve("un"), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve("deux"), 500));

Promise.race([promise1, promise2])
    .then(resultat => console.log(resultat)); // "deux"

Promise.allSettled()

Espera a que todas las promises hayan terminado (resueltas o rechazadas):

const promises = [
    Promise.resolve(1),
    Promise.reject("erreur"),
    Promise.resolve(3)
];

Promise.allSettled(promises)
    .then(resultats => {
        resultats.forEach(resultat => {
            if (resultat.status === "fulfilled") {
                console.log("Succès:", resultat.value);
            } else {
                console.log("Échec:", resultat.reason);
            }
        });
    });

Async/Await

async/await es una sintaxis más elegante para trabajar con Promises.

Funciones asíncronas

async function obtenirDonnees() {
    try {
        const utilisateur = await obtenirUtilisateur(1);
        const commandes = await obtenirCommandes(utilisateur);
        return commandes;
    } catch (erreur) {
        console.error("Erreur:", erreur);
        throw erreur;
    }
}

// Utilisation
obtenirDonnees()
    .then(resultat => console.log(resultat))
    .catch(erreur => console.error(erreur));

Con funciones flecha

const obtenirDonnees = async () => {
    const utilisateur = await obtenirUtilisateur(1);
    const commandes = await obtenirCommandes(utilisateur);
    return commandes;
};

Paralelización con async/await

async function obtenirTout() {
    try {
        // Exécution parallèle
        const [utilisateurs, produits] = await Promise.all([
            obtenirUtilisateurs(),
            obtenirProduits()
        ]);
        
        return { utilisateurs, produits };
    } catch (erreur) {
        console.error("Erreur:", erreur);
        throw erreur;
    }
}

Bucles asíncronos

async function traiterUtilisateurs(utilisateurs) {
    // Séquentiel
    for (const utilisateur of utilisateurs) {
        await envoyerEmail(utilisateur);
    }
    
    // Parallèle
    const promesses = utilisateurs.map(utilisateur => envoyerEmail(utilisateur));
    await Promise.all(promesses);
}

Patrones y buenas prácticas

Patrón de retry

async function avecRetry(fn, maxTentatives = 3) {
    let dernierErreur;
    
    for (let tentative = 1; tentative <= maxTentatives; tentative++) {
        try {
            return await fn();
        } catch (erreur) {
            dernierErreur = erreur;
            console.log(`Tentative ${tentative} échouée`);
            if (tentative < maxTentatives) {
                await new Promise(resolve => 
                    setTimeout(resolve, 1000 * tentative)
                );
            }
        }
    }
    
    throw dernierErreur;
}

// Utilisation
const fetchAvecRetry = () => 
    avecRetry(() => fetch('https://api.example.com/data'));

Timeout para las Promises

function avecTimeout(promesse, delai) {
    const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Timeout')), delai);
    });
    
    return Promise.race([promesse, timeoutPromise]);
}

// Utilisation
avecTimeout(fetch('https://api.example.com/data'), 5000)
    .then(response => response.json())
    .catch(erreur => console.error('Erreur ou timeout:', erreur));

Gestión de errores

async function gererErreurs() {
    try {
        const donnees = await fetch('https://api.example.com/data');
        
        if (!donnees.ok) {
            throw new Error(`HTTP error! status: ${donnees.status}`);
        }
        
        const resultat = await donnees.json();
        return resultat;
        
    } catch (erreur) {
        if (erreur instanceof TypeError) {
            console.error('Erreur réseau:', erreur);
        } else if (erreur.name === 'AbortError') {
            console.error('Requête annulée:', erreur);
        } else {
            console.error('Autre erreur:', erreur);
        }
        throw erreur;
    }
}

Casos de uso prácticos

Peticiones HTTP con fetch

async function obtenirDonneesAPI() {
    try {
        const response = await fetch('https://api.example.com/data', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ key: 'value' })
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
        
    } catch (error) {
        console.error('Erreur:', error);
        throw error;
    }
}

Carga de imágenes

function chargerImage(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = () => reject(new Error(`Échec du chargement de ${url}`));
        img.src = url;
    });
}

async function chargerGalerie(urls) {
    try {
        const images = await Promise.all(
            urls.map(url => chargerImage(url))
        );
        return images;
    } catch (erreur) {
        console.error('Erreur de chargement:', erreur);
        throw erreur;
    }
}

Caché con Promises

class CachePromise {
    constructor() {
        this.cache = new Map();
    }
    
    async get(key, fetchFn) {
        if (this.cache.has(key)) {
            return this.cache.get(key);
        }
        
        const promise = fetchFn();
        this.cache.set(key, promise);
        
        try {
            await promise;
            return promise;
        } catch (erreur) {
            this.cache.delete(key);
            throw erreur;
        }
    }
}

// Utilisation
const cache = new CachePromise();

async function obtenirUtilisateur(id) {
    return cache.get(`utilisateur-${id}`, async () => {
        const response = await fetch(`/api/utilisateurs/${id}`);
        return response.json();
    });
}

Ejercicios prácticos

  1. Simulación de una cola de espera
class FileAttente {
    constructor() {
        this.taches = [];
    }
    
    ajouterTache(tache) {
        return new Promise((resolve, reject) => {
            this.taches.push({ tache, resolve, reject });
            if (this.taches.length === 1) {
                this.executerSuivant();
            }
        });
    }
    
    async executerSuivant() {
        if (this.taches.length === 0) return;
        
        const { tache, resolve, reject } = this.taches[0];
        
        try {
            const resultat = await tache();
            resolve(resultat);
        } catch (erreur) {
            reject(erreur);
        } finally {
            this.taches.shift();
            this.executerSuivant();
        }
    }
}

// Utilisation
const file = new FileAttente();

file.ajouterTache(() => new Promise(resolve => 
    setTimeout(() => resolve("Tâche 1"), 1000)
));

file.ajouterTache(() => new Promise(resolve => 
    setTimeout(() => resolve("Tâche 2"), 500)
));
  1. Gestor de peticiones concurrentes
class GestionnaireRequetes {
    constructor(maxConcurrent = 3) {
        this.maxConcurrent = maxConcurrent;
        this.actives = 0;
        this.file = [];
    }
    
    async ajouter(fn) {
        if (this.actives >= this.maxConcurrent) {
            await new Promise(resolve => this.file.push(resolve));
        }
        
        this.actives++;
        
        try {
            return await fn();
        } finally {
            this.actives--;
            if (this.file.length > 0) {
                const suivant = this.file.shift();
                suivant();
            }
        }
    }
}

// Utilisation
const gestionnaire = new GestionnaireRequetes(2);

async function effectuerRequetes() {
    const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
    
    const promesses = urls.map(url => 
        gestionnaire.ajouter(() => fetch(url))
    );
    
    return Promise.all(promesses);
}

Conclusión

Las Promises y async/await son herramientas fundamentales para gestionar la asincronía en el JavaScript moderno:

  • Las Promises ofrecen un enfoque estructurado para gestionar las operaciones asíncronas
  • async/await simplifica la sintaxis y hace que el código sea más legible
  • Patrones como el retry, el timeout y el caché son esenciales para crear aplicaciones robustas
  • Una gestión adecuada de los errores es crucial para la fiabilidad de las aplicaciones

En el próximo tutorial exploraremos los módulos ES y cómo organizar eficazmente tu código JavaScript.


¿Necesitas repasar la programación orientada a objetos? Consulta nuestro tutorial anterior sobre POO.

Comentarios

Los comentarios funcionan con GitHub Discussions

Inicia sesión con GitHub para participar en la discusión

¡Enlace copiado!