Promises و Async
البرمجة غير المتزامنة الحديثة
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();
});
}
تمارين عملية
- محاكاة قائمة انتظار
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)
));
- مدير الطلبات المتزامنة
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 للمشاركة في النقاش