0%
JavaScript الحديثة: Promises و Async/Await

Promises و Async

البرمجة غير المتزامنة الحديثة

10-15 دقيقة

JavaScript الحديثة: Promises و Async/Await

تُعدّ البرمجة غير المتزامنة في صميم JavaScript الحديثة. وتُعتبر Promises و async/await أدوات أساسية للتعامل مع العمليات غير المتزامنة بأسلوب أنيق وقابل للصيانة.

الـ Promises

الـ Promise هي كائن يمثّل الاكتمال (أو الفشل) المحتمل لعملية غير متزامنة.

إنشاء 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));

حالات الـ Promise

يمكن أن تكون الـ Promise في إحدى الحالات الثلاث التالية:

  • Pending: الحالة الأولية، لم تتحقق ولم تُرفض بعد
  • Fulfilled: نجحت العملية
  • Rejected: فشلت العملية
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" }

تسلسل الـ Promises

يمكن ربط الـ Promises في سلسلة لتنفيذ العمليات بشكل متتابع:

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));

دوال مفيدة مع الـ Promises

Promise.all()

تنتظر حتى تكتمل جميع الـ promises:

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()

تُرجع أول promise تكتمل أو تُرفض:

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()

تنتظر حتى تنتهي جميع الـ promises (سواء اكتملت أو رُفضت):

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 هي صياغة أكثر أناقة للعمل مع الـ Promises.

الدوال غير المتزامنة

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));

مع الدوال السهمية

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

التنفيذ المتوازي مع 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;
    }
}

الحلقات غير المتزامنة

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);
}

الأنماط وأفضل الممارسات

نمط إعادة المحاولة (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) للـ 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));

معالجة الأخطاء

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;
    }
}

حالات استخدام عملية

طلبات HTTP باستخدام 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;
    }
}

تحميل الصور

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;
    }
}

ذاكرة تخزين مؤقت (Cache) باستخدام 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();
    });
}

تمارين عملية

  1. محاكاة قائمة انتظار
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. مدير الطلبات المتزامنة
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);
}

الخاتمة

تُعدّ Promises و async/await أدوات أساسية للتعامل مع اللاتزامن في JavaScript الحديثة:

  • توفّر Promises أسلوبًا منظّمًا للتعامل مع العمليات غير المتزامنة
  • يبسّط async/await الصياغة ويجعل الشيفرة أكثر وضوحًا
  • تُعدّ أنماط مثل إعادة المحاولة (retry) والمهلة الزمنية (timeout) والتخزين المؤقت (cache) ضرورية لبناء تطبيقات متينة
  • تُعدّ المعالجة السليمة للأخطاء أمرًا حاسمًا لموثوقية التطبيقات

في الدرس القادم، سنستكشف وحدات ES وكيفية تنظيم شيفرة JavaScript الخاصة بك بكفاءة.


هل تحتاج إلى مراجعة البرمجة كائنية التوجه؟ اطلع على الدرس السابق حول البرمجة كائنية التوجه.

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!