مقدمه
در مقاله Callback Function دیدیم که وقتی عملیاتهای async زیاد میشوند، کد به Callback Hell تبدیل میشود. Promise در ES6 معرفی شد تا این مشکل را حل کند.
// ❌ Callback Hell — ۴ سطح عمق
getUser(id, function(err, user) {
if (err) return handleError(err);
getArticles(user.id, function(err, articles) {
if (err) return handleError(err);
getComments(articles[0].id, function(err, comments) {
if (err) return handleError(err);
renderPage(user, articles, comments, function(err) {
if (err) return handleError(err);
console.log('صفحه نمایش داده شد');
});
});
});
});
// ✅ با Promise — زنجیره خطی
getUser(id)
.then(user => Promise.all([Promise.resolve(user), getArticles(user.id)]))
.then(([user, articles]) => Promise.all([user, articles, getComments(articles[0].id)]))
.then(([user, articles, comments]) => renderPage(user, articles, comments))
.then(() => console.log('صفحه نمایش داده شد'))
.catch(handleError); // یک catch برای همه خطاها
Promise چیست؟
Promise یک شیء (Object) است که نمایانگر نتیجه یک عملیات async است. نامش از دنیای واقعی میآید — مثل یک قول: «قول میدهم این کار را انجام دهم — یا موفق میشوم یا دلیل شکستم را اعلام میکنم.»
یک Promise در یکی از سه حالت زیر قرار دارد:
Pending (در انتظار) — عملیات هنوز در جریان است
Fulfilled (موفق) — عملیات با موفقیت تمام شد و نتیجه آماده است
Rejected (شکست خورده) — عملیات با خطا مواجه شد
وقتی Promise از Pending خارج شد (Fulfilled یا Rejected)، دیگر تغییر نمیکند — این را immutable میگویند.
const promise = new Promise((resolve, reject) => {
console.log('Promise ساخته شد — Pending است');
setTimeout(() => {
const success = true;
if (success) {
resolve('عملیات موفق شد! ✅'); // Fulfilled
} else {
reject(new Error('عملیات شکست خورد ❌')); // Rejected
}
}, 1000);
});
console.log(promise); // Promise { }
promise
.then(result => console.log(result)) // 'عملیات موفق شد! ✅'
.catch(error => console.error(error)); // اگر شکست بخورد
ساختن Promise
// ─── ساختار پایه ───
const myPromise = new Promise((resolve, reject) => {
// resolve(value) — وقتی کار موفق است
// reject(error) — وقتی کار شکست خورده
// مثال: شبیهسازی درخواست به سرور
setTimeout(() => {
const data = { id: 1, title: 'Promise در جاوااسکریپت' };
resolve(data);
}, 1000);
});
// ─── Promise.resolve و Promise.reject ───
const resolved = Promise.resolve('مقدار آماده');
const rejected = Promise.reject(new Error('خطا'));
resolved.then(v => console.log(v)); // 'مقدار آماده'
rejected.catch(e => console.error(e.message)); // 'خطا'
// ─── تبدیل Callback به Promise (Promisification) ───
function readArticle(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!id) { reject(new Error('شناسه الزامی است')); return; }
if (id === 999) { reject(new Error('مقاله یافت نشد')); return; }
resolve({ id, title: 'مقاله شماره ' + id, views: 1500 });
}, 500);
});
}
// ─── تبدیل setTimeout به Promise ───
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function demo() {
console.log('شروع');
await wait(2000);
console.log('۲ ثانیه گذشت');
}
then، catch و finally
readArticle(1)
.then(article => {
console.log('مقاله:', article.title);
return article; // این به .then بعدی میرود
})
.then(article => {
console.log('بازدید:', article.views);
})
.catch(error => {
console.error('خطا:', error.message);
})
.finally(() => {
console.log('عملیات تمام شد'); // همیشه اجرا میشود
hideLoadingSpinner();
});
// ─── finally کاربرد واقعی ───
async function fetchWithFinally(url) {
showLoading();
return fetch(url)
.then(res => {
if (!res.ok) throw new Error('خطای HTTP: ' + res.status);
return res.json();
})
.catch(err => {
showError(err.message);
return null;
})
.finally(() => {
hideLoading(); // همیشه loading را مخفی کن — موفق یا شکست
});
}
Promise Chaining — زنجیره Promise
// هر .then یک Promise جدید برمیگرداند
// مقدار return شده در .then به .then بعدی پاس داده میشود
fetch('https://freeapi.codeloop.ir/users/1')
.then(response => {
if (!response.ok) throw new Error('خطای HTTP: ' + response.status);
return response.json(); // این به .then بعدی میرود
})
.then(user => {
console.log('کاربر:', user.name);
// برگرداندن Promise جدید — .then بعدی منتظر آن میماند
return fetch('https://freeapi.codeloop.ir/products?userId=' + user.id);
})
.then(response => response.json())
.then(products => {
console.log('محصولات:', products.length);
return products.filter(p => p.price > 500000);
})
.then(expensive => {
console.log('محصولات گرانقیمت:', expensive.length);
})
.catch(error => {
// یک catch برای همه خطاها در زنجیره
console.error('خطا در هر مرحله:', error.message);
});
// ─── نکته: throw در .then به .catch میرود ───
fetch('https://freeapi.codeloop.ir/products')
.then(res => {
if (!res.ok) throw new Error('خطا'); // به .catch میرود
return res.json();
})
.then(data => {
if (data.length === 0) throw new Error('هیچ محصولی نیست'); // به .catch میرود
return data;
})
.catch(err => console.error(err.message));
Promise.all — همه با هم
// Promise.all: منتظر میماند همه تمام شوند
// اگر یکی شکست بخورد، همه شکست میخورند
const p1 = fetch('https://freeapi.codeloop.ir/products').then(r => r.json());
const p2 = fetch('https://freeapi.codeloop.ir/users').then(r => r.json());
const p3 = fetch('https://freeapi.codeloop.ir/categories').then(r => r.json());
Promise.all([p1, p2, p3])
.then(([products, users, categories]) => {
console.log('محصولات:', products.length);
console.log('کاربران:', users.length);
console.log('دستهبندیها:', categories.length);
})
.catch(error => {
console.error('یکی از درخواستها شکست خورد:', error.message);
});
// ─── با async/await ───
async function loadAll() {
const [products, users, categories] = await Promise.all([
fetch('https://freeapi.codeloop.ir/products').then(r => r.json()),
fetch('https://freeapi.codeloop.ir/users').then(r => r.json()),
fetch('https://freeapi.codeloop.ir/categories').then(r => r.json()),
]);
return { products, users, categories };
}
// ─── Promise.all با زمانبندی ───
const fast = new Promise(resolve => setTimeout(() => resolve('سریع'), 300));
const medium = new Promise(resolve => setTimeout(() => resolve('متوسط'), 700));
const slow = new Promise(resolve => setTimeout(() => resolve('کند'), 1500));
Promise.all([fast, medium, slow])
.then(results => console.log(results));
// بعد از ۱۵۰۰ms: ['سریع', 'متوسط', 'کند']
Promise.allSettled — همه نتایج
// Promise.allSettled: منتظر میماند همه تمام شوند
// حتی اگر بعضی شکست بخورند — همه نتایج را برمیگرداند
const promises = [
fetch('https://freeapi.codeloop.ir/products').then(r => r.json()),
Promise.reject(new Error('این endpoint وجود ندارد')),
fetch('https://freeapi.codeloop.ir/users').then(r => r.json()),
Promise.reject(new Error('خطای شبکه')),
];
Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log('درخواست ' + (index + 1) + ' موفق:', result.value.length, 'آیتم');
} else {
console.log('درخواست ' + (index + 1) + ' شکست:', result.reason.message);
}
});
});
// خروجی:
// درخواست 1 موفق: 20 آیتم
// درخواست 2 شکست: این endpoint وجود ندارد
// درخواست 3 موفق: 10 آیتم
// درخواست 4 شکست: خطای شبکه
Promise.race — اولین نتیجه
// Promise.race: اولین Promise که تمام شود — موفق یا شکست
const fast = new Promise(resolve => setTimeout(() => resolve('سریع'), 200));
const slow = new Promise(resolve => setTimeout(() => resolve('کند'), 2000));
Promise.race([fast, slow])
.then(result => console.log('برنده:', result));
// 'برنده: سریع'
// ─── کاربرد واقعی: Timeout برای درخواست ───
function fetchWithTimeout(url, timeoutMs) {
const fetchPromise = fetch(url).then(r => {
if (!r.ok) throw new Error('خطای HTTP: ' + r.status);
return r.json();
});
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('درخواست timeout شد — ' + timeoutMs + 'ms')), timeoutMs)
);
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout('https://freeapi.codeloop.ir/products', 3000)
.then(data => console.log('موفق:', data.length))
.catch(err => console.error('خطا:', err.message));
Promise.any — اولین موفق
// Promise.any: اولین Promise که موفق شود
// اگر همه شکست بخورند، AggregateError میدهد
const p1 = Promise.reject(new Error('p1 شکست'));
const p2 = new Promise(resolve => setTimeout(() => resolve('p2 موفق'), 500));
const p3 = new Promise(resolve => setTimeout(() => resolve('p3 موفق'), 100));
Promise.any([p1, p2, p3])
.then(result => console.log(result)) // 'p3 موفق' — سریعترین موفق
.catch(err => console.error(err));
// ─── کاربرد: Fallback به سرور دیگر ───
const servers = [
'https://api1.example.com/products',
'https://api2.example.com/products',
'https://freeapi.codeloop.ir/products', // fallback
];
Promise.any(servers.map(url =>
fetch(url).then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
))
.then(data => console.log('سریعترین سرور جواب داد:', data.length))
.catch(() => console.error('هیچ سروری در دسترس نیست'));
جدول مقایسه متدهای Promise
متد | رفتار | شکست یکی | کاربرد |
|---|---|---|---|
Promise.all | منتظر همه | همه شکست | وقتی به همه نیاز دارید |
Promise.allSettled | منتظر همه | نتیجه همه | وقتی میخواهید همه ببینید |
Promise.race | اولین که تمام شود | اگر اول باشد | Timeout، سریعترین |
Promise.any | اولین موفق | اگر همه شکست | Fallback |
مثال کامل واقعی — داشبورد codeloop.ir
// بارگذاری همزمان دادههای داشبورد
function showLoading() { console.log('⏳ در حال بارگذاری...'); }
function hideLoading() { console.log('✅ بارگذاری تمام شد'); }
function createApiRequest(url) {
return fetch(url).then(res => {
if (!res.ok) throw new Error('خطای HTTP ' + res.status + ' برای ' + url);
return res.json();
});
}
async function loadDashboard() {
showLoading();
try {
// درخواستهای ضروری — همه با هم
const [products, users, categories] = await Promise.all([
createApiRequest('https://freeapi.codeloop.ir/products'),
createApiRequest('https://freeapi.codeloop.ir/users'),
createApiRequest('https://freeapi.codeloop.ir/categories'),
]);
// آمار
const stats = {
totalProducts: products.length,
inStockProducts: products.filter(p => p.inStock !== false).length,
totalUsers: users.length,
totalCategories: categories.length,
avgPrice: Math.round(products.reduce((s, p) => s + p.price, 0) / products.length),
topProducts: [...products]
.sort((a, b) => b.price - a.price)
.slice(0, 3)
.map(p => ({ name: p.name, price: p.price.toLocaleString('fa') + ' تومان' })),
};
console.log('آمار داشبورد:', stats);
return stats;
} catch (error) {
console.error('خطا در بارگذاری داشبورد:', error.message);
// Fallback: سعی کنید حداقل یکی را بارگذاری کنید
const fallbackResults = await Promise.allSettled([
createApiRequest('https://freeapi.codeloop.ir/products'),
createApiRequest('https://freeapi.codeloop.ir/users'),
]);
const successfulData = fallbackResults
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
console.log('دادههای جزئی:', successfulData.length, 'از ۲ درخواست موفق شدند');
return null;
} finally {
hideLoading();
}
}
loadDashboard();
💡 برای تمرین عملی
با استفاده از API رایگان فارسی کدلوپ Promise را تمرین کنید:
تمرین ۱ — Promise.all با چند endpoint
Promise.all([
fetch('https://freeapi.codeloop.ir/products').then(r => r.json()),
fetch('https://freeapi.codeloop.ir/categories').then(r => r.json()),
])
.then(([products, categories]) => {
// ترکیب دادهها
const enriched = products.map(product => ({
...product,
categoryName: categories.find(c => c.id === product.categoryId)?.name || 'نامشخص',
formattedPrice: product.price.toLocaleString('fa') + ' تومان',
}));
console.log('محصولات با دستهبندی:', enriched.slice(0, 3));
})
.catch(console.error);
تمرین ۲ — Retry با Promise
function fetchWithRetry(url, retries = 3, delay = 1000) {
return fetch(url)
.then(res => {
if (!res.ok) throw new Error('خطای HTTP: ' + res.status);
return res.json();
})
.catch(error => {
if (retries <= 0) throw error;
console.log('تلاش مجدد... ' + retries + ' بار باقی مانده');
return new Promise(resolve => setTimeout(resolve, delay))
.then(() => fetchWithRetry(url, retries - 1, delay * 2));
});
}
fetchWithRetry('https://freeapi.codeloop.ir/products', 3, 500)
.then(data => console.log('موفق:', data.length, 'محصول'))
.catch(err => console.error('همه تلاشها شکست خورد:', err.message));
تمرین ۳ — Promise Queue (صف)
// اجرای Promise ها به ترتیب — نه موازی
async function promiseQueue(tasks) {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
console.log('تسک تمام شد:', result);
}
return results;
}
// ایجاد تسکها — هر تسک یک تابع است که Promise برمیگرداند
const tasks = [1, 2, 3].map(page => () =>
fetch('https://freeapi.codeloop.ir/products?page=' + page)
.then(r => r.json())
.then(data => ({ page, count: data.length }))
);
promiseQueue(tasks).then(results => console.log('همه نتایج:', results));
اشتباهات رایج
/* ─── اشتباه ۱: فراموش کردن return در .then ─── */
fetch('https://freeapi.codeloop.ir/products')
.then(res => {
res.json(); // ❌ return فراموش شد!
})
.then(data => {
console.log(data); // undefined
});
// ✅
fetch('https://freeapi.codeloop.ir/products')
.then(res => res.json()) // implicit return ✅
.then(data => console.log(data));
/* ─── اشتباه ۲: Promise داخل Promise (anti-pattern) ─── */
getUser(id).then(user => {
getArticles(user.id).then(articles => { // ❌ داخل .then
console.log(articles);
});
});
// ✅ زنجیره درست
getUser(id)
.then(user => getArticles(user.id)) // ← return کنید
.then(articles => console.log(articles));
/* ─── اشتباه ۳: نادیده گرفتن خطا ─── */
fetch('https://freeapi.codeloop.ir/products')
.then(res => res.json())
.then(data => console.log(data));
// ❌ بدون .catch — خطاها بیصدا از بین میروند!
// ✅
fetch('https://freeapi.codeloop.ir/products')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('خطا:', err.message));
/* ─── اشتباه ۴: Promise.all وقتی شکست یکی مهم نیست ─── */
// ❌ اگر products شکست بخورد، همه از دست میرود
Promise.all([fetchProducts(), fetchUsers(), fetchOptional()])
.then(([p, u, o]) => { /* ... */ });
// ✅ از Promise.allSettled استفاده کنید
Promise.allSettled([fetchProducts(), fetchUsers(), fetchOptional()])
.then(([pResult, uResult, oResult]) => {
const products = pResult.status === 'fulfilled' ? pResult.value : [];
const users = uResult.status === 'fulfilled' ? uResult.value : [];
const optional = oResult.status === 'fulfilled' ? oResult.value : null;
});
نکات مهم
Promise سه حالت دارد — Pending، Fulfilled، Rejected. وقتی از Pending خارج شد، دیگر تغییر نمیکند.
همیشه .catch بنویسید — خطاهای Promise بدون .catch بیصدا از بین میروند.
return در .then فراموش نکنید — بدون return، .then بعدی undefined میگیرد.
Promise.all برای درخواستهای موازی — به جای یکی یکی، همه را با هم اجرا کنید تا سریعتر باشد.
async/await روی Promise ساخته شده — async/await فقط یک سینتکس بهتر برای Promise است، نه چیز جدیدی.
نتیجهگیری
Promise یکی از مهمترین ویژگیهای ES6 است که برنامهنویسی async در جاوااسکریپت را متحول کرد. از Callback Hell به زنجیره خطی رفتیم — و Promise پایهای شد که async/await روی آن ساخته شد.
مسیر یادگیری پیشنهادی:
روز ۱: ساختن Promise با new Promise و resolve/reject
روز ۲: then، catch، finally — و زنجیره کردن
روز ۳: Promise.all، allSettled، race، any — با مثالهای واقعی
روز ۴: تبدیل Callback به Promise و ترکیب با async/await
Promise انقلابی در جاوااسکریپت بود — اما async/await آن را به سطح بعدی برد. وقتی هر دو را بدانید، هیچ کدی async برایتان ترسناک نخواهد بود.
مقالات مرتبط
Callback Function در جاوااسکریپت — مشکلی که Promise حل کرد
async/await در جاوااسکریپت — سینتکس مدرنتر بر پایه Promise
Fetch API در جاوااسکریپت — پرکاربردترین جای Promise
مدیریت خطا در جاوااسکریپت — مدیریت خطا در Promise
اسکوپ و کلوژر در جاوااسکریپت — Promise و Closure رابطه نزدیکی دارند

