مقدمه
تصور کنید به پیتزافروشی زنگ میزنید و سفارش میدهید. صاحب مغازه میگوید: «پیتزا ۳۰ دقیقه طول میکشد — وقتی آماده شد زنگ میزنیم.» شما گوشی را میگذارید و کار دیگری میکنید. شماره تلفن شما همان Callback است — «وقتی آماده شد، این تابع را صدا بزن.»
// بدون Callback — برنامه قفل میشود (مفهومی)
const pizza = waitForPizza(); // ۳۰ دقیقه صبر میکنیم — هیچ کار دیگری نمیشود کرد!
eat(pizza);
// با Callback — برنامه ادامه میدهد
orderPizza(function(pizza) {
// وقتی پیتزا آماده شد، این تابع صدا زده میشود
eat(pizza);
});
doOtherThings(); // این بلافاصله اجرا میشود — منتظر پیتزا نمیمانیم!
Callback Function پایهی برنامهنویسی async در جاوااسکریپت است. درک آن برای فهمیدن Promise، async/await و در واقع هر کتابخانه و فریمورک مدرنی ضروری است.
Callback Function چیست؟
Callback Function تابعی است که:
به عنوان آرگومان به تابع دیگری پاس داده میشود
توسط آن تابع، در زمان مناسب فراخوانی میشود
معمولاً بعد از تمام شدن یک عملیات اجرا میشود
// سادهترین مثال Callback
function greet(name, callback) {
console.log('سلام ' + name + '!');
callback(); // اینجا Callback اجرا میشود
}
function sayGoodbye() {
console.log('خداحافظ!');
}
// سه روش پاس دادن Callback
greet('رضا', sayGoodbye); // Named Function
greet('علی', function() { console.log('ممنون!'); }); // Anonymous Function
greet('مریم', () => console.log('موفق باشید!')); // Arrow Function
// خروجی هر سه:
// سلام [نام]!
// [پیام]
چرا Callback لازم است؟
جاوااسکریپت Single-Threaded است — یعنی در هر لحظه فقط یک کار انجام میدهد. اگر منتظر بمانیم یک عملیات طولانی (مثل درخواست API) تمام شود، کل برنامه قفل میشود و UI برای کاربر منجمد میشود.
// ─── بدون Callback — UI قفل میشود (مفهومی) ───
function loadArticles() {
const articles = fetchFromServer(); // فرض: ۳ ثانیه طول میکشد
// در این ۳ ثانیه، کاربر نمیتواند روی هیچ چیزی کلیک کند!
return articles;
}
// ─── با Callback — UI واکنشپذیر میماند ───
function loadArticles(onSuccess, onError) {
setTimeout(function() {
const success = true;
if (success) {
const articles = [{ id: 1, title: 'اسکوپ در جاوااسکریپت' }];
onSuccess(articles); // وقتی آماده شد، callback را صدا بزن
} else {
onError(new Error('خطا در بارگذاری'));
}
}, 3000); // ۳ ثانیه
}
loadArticles(
function(articles) { console.log('مقالات:', articles); },
function(error) { console.error('خطا:', error.message); }
);
console.log('این بلافاصله اجرا میشود — منتظر مقالات نمیمانیم!');
renderLoadingSpinner();
Callback در Array Methods — پرکاربردترین جا
const articles = [
{ title: 'جاوااسکریپت چیست', views: 5200, category: 'javascript', published: true },
{ title: 'React چیست', views: 4800, category: 'react', published: true },
{ title: 'اسکوپ و کلوژر', views: 3100, category: 'javascript', published: true },
{ title: 'Node.js چیست', views: 1500, category: 'nodejs', published: true },
{ title: 'Draft Article', views: 0, category: 'misc', published: false },
];
// forEach — برای هر آیتم یک کار انجام بده (side effect)
articles.forEach(function(article, index) {
console.log((index + 1) + '. ' + article.title + ' — ' + article.views + ' بازدید');
});
// map — تبدیل هر آیتم به چیز دیگری
const titles = articles.map(article => article.title);
const withViews = articles.map(a => ({
title: a.title,
views: a.views.toLocaleString('fa') + ' بازدید',
}));
// filter — فیلتر بر اساس شرط
const published = articles.filter(a => a.published);
const jsArticles = articles.filter(a => a.category === 'javascript');
const popular = articles.filter(a => a.views > 3000 && a.published);
// find — اولین مورد مطابق
const firstJs = articles.find(a => a.category === 'javascript');
console.log(firstJs.title); // 'جاوااسکریپت چیست'
// sort — مرتبسازی
const sorted = [...articles].sort((a, b) => b.views - a.views);
// reduce — جمع بازدیدها
const totalViews = articles.reduce((sum, a) => sum + a.views, 0);
console.log('کل بازدید:', totalViews.toLocaleString('fa')); // ۱۴,۶۰۰
// every / some
const allPublished = articles.every(a => a.published); // false
const hasPopular = articles.some(a => a.views > 5000); // true
console.log('همه منتشر شده؟', allPublished); // false
console.log('آیا پرطرفدار وجود دارد؟', hasPopular); // true
Callback در Event Listener
// ─── کلیک ───
const submitBtn = document.querySelector('#submit-btn');
submitBtn.addEventListener('click', function(event) {
event.preventDefault();
console.log('فرم ارسال شد!');
handleFormSubmit();
});
// ─── Input در زمان واقعی ───
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', function(e) {
const query = e.target.value;
console.log('در حال جستجو:', query);
if (query.length > 2) performSearch(query);
});
// ─── چند event روی یک المان ───
const card = document.querySelector('.article-card');
card.addEventListener('mouseenter', (e) => e.target.classList.add('hovered'));
card.addEventListener('mouseleave', (e) => e.target.classList.remove('hovered'));
card.addEventListener('click', (e) => navigateTo(e.target.dataset.slug));
// ─── Event Delegation — یک Callback برای همه ───
document.querySelector('.articles-grid').addEventListener('click', function(e) {
const card = e.target.closest('.article-card');
if (!card) return;
if (e.target.matches('.like-btn')) {
handleLike(card.dataset.id);
} else if (e.target.matches('.share-btn')) {
handleShare(card.dataset.slug);
} else {
navigateTo('/articles/' + card.dataset.slug);
}
});
Error-First Callback — الگوی Node.js
// در Node.js یک قرارداد رایج: اولین پارامتر Callback همیشه برای خطا
function readArticle(id, callback) {
setTimeout(function() {
if (!id) {
callback(new Error('شناسه مقاله الزامی است'), null);
return; // ← مهم! بدون return، ادامه اجرا میشود
}
if (typeof id !== 'number') {
callback(new Error('شناسه باید عدد باشد'), null);
return;
}
if (id === 999) {
callback(new Error('مقاله یافت نشد'), null);
return;
}
const article = {
id,
title: 'مقاله شماره ' + id,
content: 'محتوای مقاله...',
views: Math.floor(Math.random() * 5000),
};
callback(null, article); // null = بدون خطا، article = داده
}, 500);
}
// استفاده — همیشه اول خطا را چک کنید
readArticle(1, function(error, data) {
if (error) {
console.error('خطا:', error.message);
return;
}
console.log('مقاله:', data.title);
});
readArticle(999, function(error, data) {
if (error) {
console.error('خطا:', error.message); // 'مقاله یافت نشد'
return;
}
console.log('مقاله:', data);
});
readArticle(null, function(error, data) {
if (error) {
console.error('خطا:', error.message); // 'شناسه مقاله الزامی است'
return;
}
});
Callback Hell چیست؟
وقتی Callbackها تو در تو میشوند، کد به شکل هرم درمیآید — به این Callback Hell یا Pyramid of Doom میگویند.
// ❌ Callback Hell — کد واقعی از پروژههای قدیمی
function loadDashboard(userId) {
getUser(userId, function(err, user) {
if (err) { handleError(err); return; }
getArticlesByUser(user.id, function(err, articles) {
if (err) { handleError(err); return; }
getCommentsForArticles(articles, function(err, comments) {
if (err) { handleError(err); return; }
getUserPreferences(user.id, function(err, prefs) {
if (err) { handleError(err); return; }
getNotifications(user.id, function(err, notifications) {
if (err) { handleError(err); return; }
// بالاخره به همه دادهها رسیدیم — ۵ سطح عمق!
renderDashboard(user, articles, comments, prefs, notifications, function(err) {
if (err) { handleError(err); return; }
console.log('داشبورد نمایش داده شد');
// ۶ سطح عمق — شکل هرم (Pyramid of Doom)
});
});
});
});
});
});
}
مشکلات Callback Hell:
خوانایی پایین — کد به شکل هرم درمیآید و پیدا کردن منطق اصلی سخت است
مدیریت خطا تکراری — هر سطح باید خطا را جداگانه مدیریت کند
Debug سخت — stack trace گیجکننده است
تغییر سخت — اضافه کردن یک مرحله جدید کل ساختار را بهم میزند
تست سخت — نوشتن unit test برای این کد بسیار دشوار است
راهحلهای Callback Hell
راهحل ۱ — Named Functions
// ✅ هر مرحله یک تابع مجزا — کد تمیز و قابل تست
function handleNotifications(err, notifications) {
if (err) return handleError(err);
renderDashboard(dashboardData.user, dashboardData.articles,
dashboardData.comments, dashboardData.prefs, notifications, onRenderDone);
}
function handlePrefs(err, prefs) {
if (err) return handleError(err);
dashboardData.prefs = prefs;
getNotifications(dashboardData.user.id, handleNotifications);
}
function handleComments(err, comments) {
if (err) return handleError(err);
dashboardData.comments = comments;
getUserPreferences(dashboardData.user.id, handlePrefs);
}
function handleArticles(err, articles) {
if (err) return handleError(err);
dashboardData.articles = articles;
getCommentsForArticles(articles, handleComments);
}
const dashboardData = {};
function handleUser(err, user) {
if (err) return handleError(err);
dashboardData.user = user;
getArticlesByUser(user.id, handleArticles);
}
function loadDashboard(userId) {
getUser(userId, handleUser);
}
راهحل ۲ — Promise
// ✅ با Promise — زنجیره خطی و خوانا
function loadDashboard(userId) {
getUser(userId)
.then(user => Promise.all([Promise.resolve(user), getArticlesByUser(user.id)]))
.then(([user, articles]) => Promise.all([
Promise.resolve(user),
Promise.resolve(articles),
getCommentsForArticles(articles),
getUserPreferences(user.id),
]))
.then(([user, articles, comments, prefs]) =>
renderDashboard(user, articles, comments, prefs)
)
.catch(handleError);
}
راهحل ۳ — Async/Await (بهترین)
// ✅ با async/await — مثل کد synchronous، خیلی خوانا
async function loadDashboard(userId) {
try {
const user = await getUser(userId);
const [articles, prefs, notifications] = await Promise.all([
getArticlesByUser(user.id),
getUserPreferences(user.id),
getNotifications(user.id),
]);
const comments = await getCommentsForArticles(articles);
await renderDashboard(user, articles, comments, prefs, notifications);
console.log('داشبورد نمایش داده شد');
} catch (error) {
handleError(error);
}
}
مثال کامل واقعی — سیستم پردازش مقالات
// سیستم پردازش مقالات با Callback برای codeloop.ir
// شبیهسازی عملیات async با Error-First Callback
function validateArticle(article, callback) {
setTimeout(() => {
const errors = [];
if (!article.title || article.title.length < 5) {
errors.push('عنوان باید حداقل ۵ کاراکتر باشد');
}
if (!article.content || article.content.length < 50) {
errors.push('محتوا باید حداقل ۵۰ کاراکتر باشد');
}
if (!article.category) {
errors.push('دستهبندی الزامی است');
}
if (errors.length > 0) {
callback(new Error(errors.join('، ')), null);
} else {
callback(null, { ...article, validated: true });
}
}, 200);
}
function generateSlug(article, callback) {
setTimeout(() => {
const slug = article.title
.toLowerCase()
.trim()
.replace(/s+/g, '-')
.replace(/[^w-ۿ-]/g, '');
callback(null, { ...article, slug });
}, 100);
}
function checkDuplicate(article, callback) {
setTimeout(() => {
const existingSlugs = ['what-is-javascript', 'javascript-scope-closure'];
if (existingSlugs.includes(article.slug)) {
callback(new Error('مقاله با این عنوان قبلاً وجود دارد'), null);
} else {
callback(null, article);
}
}, 150);
}
function saveToDatabase(article, callback) {
setTimeout(() => {
const saved = {
...article,
id: Math.floor(Math.random() * 10000),
createdAt: new Date(),
status: 'published',
viewsCount: 0,
};
console.log('✅ مقاله ذخیره شد — ID:', saved.id);
callback(null, saved);
}, 300);
}
function sendNotification(article, callback) {
setTimeout(() => {
console.log('📧 اطلاعرسانی ارسال شد برای مقاله:', article.title);
callback(null, { ...article, notified: true });
}, 200);
}
// ─── پردازش با Named Functions (بدون Callback Hell) ───
function onNotified(err, article) {
if (err) { console.error('خطا در اطلاعرسانی:', err.message); return; }
console.log('فرآیند کامل شد برای:', article.title);
}
function onSaved(err, article) {
if (err) { console.error('خطا در ذخیره:', err.message); return; }
sendNotification(article, onNotified);
}
function onDuplicateChecked(err, article) {
if (err) { console.error('خطا:', err.message); return; }
saveToDatabase(article, onSaved);
}
function onSlugGenerated(err, article) {
if (err) { console.error('خطا:', err.message); return; }
checkDuplicate(article, onDuplicateChecked);
}
function onValidated(err, article) {
if (err) { console.error('خطای اعتبارسنجی:', err.message); return; }
generateSlug(article, onSlugGenerated);
}
function processArticle(articleData) {
console.log('شروع پردازش:', articleData.title);
validateArticle(articleData, onValidated);
}
// تست
processArticle({
title: 'Callback Function در جاوااسکریپت',
content: 'در این مقاله یاد میگیرید Callback چیست و چطور در پروژههای واقعی استفاده میشود. Callback پایهی برنامهنویسی async است.',
category: 'javascript',
tags: ['جاوااسکریپت', 'Callback'],
});
processArticle({
title: 'کوتاه',
content: 'خیلی کم',
// category ندارد
});
// خطای اعتبارسنجی: عنوان باید حداقل ۵ کاراکتر باشد، ...
💡 برای تمرین عملی
با استفاده از API رایگان فارسی کدلوپ Callback را تمرین کنید:
تمرین ۱ — Callback با Fetch
function fetchData(url, onSuccess, onError) {
fetch(url)
.then(response => {
if (!response.ok) throw new Error('خطای HTTP: ' + response.status);
return response.json();
})
.then(data => onSuccess(data))
.catch(error => onError(error));
}
fetchData(
'https://freeapi.codeloop.ir/products',
function(products) {
console.log('تعداد محصولات:', products.length);
const expensive = products
.filter(p => p.price > 500000)
.map(p => ({ name: p.name, price: p.price.toLocaleString('fa') + ' تومان' }));
console.log('محصولات گرانقیمت:', expensive);
},
function(error) {
console.error('خطا در دریافت محصولات:', error.message);
}
);
تمرین ۲ — پردازش زنجیرهای با Callback
// Pipeline پردازش داده با Callback
function pipe(data, ...fns) {
function next(index, currentData) {
if (index >= fns.length) {
console.log('Pipeline کامل شد:', currentData);
return;
}
fns[index](currentData, function(err, result) {
if (err) {
console.error('خطا در مرحله ' + (index + 1) + ':', err.message);
return;
}
next(index + 1, result);
});
}
next(0, data);
}
// مراحل پردازش
function fetchProducts(_, callback) {
fetch('https://freeapi.codeloop.ir/products')
.then(r => r.json())
.then(data => callback(null, data))
.catch(err => callback(err, null));
}
function filterInStock(products, callback) {
const inStock = products.filter(p => p.inStock !== false);
callback(null, inStock);
}
function sortByPrice(products, callback) {
const sorted = [...products].sort((a, b) => a.price - b.price);
callback(null, sorted);
}
function formatForDisplay(products, callback) {
const formatted = products.slice(0, 5).map(p => ({
name: p.name,
price: p.price.toLocaleString('fa') + ' تومان',
}));
callback(null, formatted);
}
// اجرای pipeline
pipe(null, fetchProducts, filterInStock, sortByPrice, formatForDisplay);
تمرین ۳ — Throttle با Callback
// Throttle — حداکثر یک بار در بازه زمانی مشخص
function throttle(fn, limit) {
let lastCall = 0;
let timer = null;
return function(...args) {
const now = Date.now();
const remaining = limit - (now - lastCall);
if (remaining <= 0) {
clearTimeout(timer);
lastCall = now;
fn.apply(this, args);
} else {
clearTimeout(timer);
timer = setTimeout(() => {
lastCall = Date.now();
fn.apply(this, args);
}, remaining);
}
};
}
// استفاده با scroll
const handleScroll = throttle(function() {
const scrollY = window.scrollY;
console.log('Scroll:', scrollY);
// بارگذاری مقالات بیشتر
if (scrollY + window.innerHeight >= document.body.scrollHeight - 100) {
loadMoreArticles();
}
}, 200); // حداکثر هر ۲۰۰ms
window.addEventListener('scroll', handleScroll);
مقایسه Callback، Promise و Async/Await
ویژگی | Callback | Promise | Async/Await |
|---|---|---|---|
خوانایی | کم (Callback Hell) | متوسط | زیاد |
مدیریت خطا | دستی (if error) | .catch() | try/catch |
Debugging | سخت | متوسط | آسان |
زنجیره عملیات | سخت | .then() chain | خطی و ساده |
پشتیبانی | همه جا | ES6+ | ES2017+ |
کاربرد امروزی | Array Methods، Events، Node.js | API calls | API calls |
اشتباهات رایج
/* ─── اشتباه ۱: فراموش کردن return بعد از callback خطا ─── */
function processData(data, callback) {
if (!data) {
callback(new Error('داده خالی است'));
// ❌ بدون return — کد ادامه میدهد!
// ممکن است callback دو بار صدا زده شود
}
callback(null, process(data)); // با داده null این crash میکند
}
// ✅
function processData(data, callback) {
if (!data) {
callback(new Error('داده خالی است'));
return; // ← متوقف میشود
}
callback(null, process(data));
}
/* ─── اشتباه ۲: فراموش کردن مدیریت خطا ─── */
getUser(id, function(error, user) {
// ❌ خطا چک نشد!
console.log(user.name); // اگر خطا باشد، crash
});
// ✅
getUser(id, function(error, user) {
if (error) {
console.error('خطا:', error.message);
return;
}
console.log(user.name); // ✅ مطمئن هستیم user وجود دارد
});
/* ─── اشتباه ۳: Callback Hell به جای Named Function ─── */
// ❌
doA(function() {
doB(function() {
doC(function() {
doD(function() {
console.log('تمام'); // خوانده نمیشود!
});
});
});
});
// ✅ Named Function یا Promise/async-await
/* ─── اشتباه ۴: استفاده از این Pattern در کد مدرن ─── */
// اگر از Node.js 12+ یا مرورگرهای مدرن استفاده میکنید:
// ✅ برای Array Methods — Callback هنوز درست است
// ✅ برای Event Listener — Callback هنوز درست است
// ❌ برای async operations — از Promise یا async/await استفاده کنید
نکات مهم
Callback پایه همه چیز است — Promise و async/await هم در پشت صحنه از Callback استفاده میکنند. درک Callback درک عمیقتر JavaScript است.
Error-First Callback — در Node.js اولین پارامتر همیشه برای خطا است. همیشه آن را چک کنید و return بزنید.
Callback Hell را با Named Function حل کنید — یا بهتر از آن، از Promise و async/await استفاده کنید.
return بعد از callback خطا — فراموش نکنید وگرنه کد ادامه مییابد و callback دو بار صدا زده میشود.
Array Methods هنوز Callback دارند — map، filter، reduce و forEach همه از Callback استفاده میکنند و این استفاده کاملاً درست و مدرن است.
نتیجهگیری
Callback Function پایهی برنامهنویسی async در جاوااسکریپت است. درک آن برای فهمیدن Promise، async/await، Node.js و هر فریمورکی ضروری است.
در کد مدرن:
برای Array Methods و Event Listener — Callback هنوز بهترین روش است
برای عملیات async — از Promise یا async/await استفاده کنید
برای Node.js APIs — بسیاری هنوز Error-First Callback دارند
وقتی Callback را کاملاً درک کنید، Promise دیگر جادو نیست — فقط یک روش تمیزتر برای مدیریت عملیات async است. و async/await فقط یک سینتکس بهتر روی Promise است. همه چیز از Callback شروع میشود.
مقالات مرتبط
تابع در جاوااسکریپت — پایهی درک Callback Function
آرایه در جاوااسکریپت — پرکاربردترین جای Callback در Array Methods
Promise در جاوااسکریپت — راهحل Callback Hell
async/await در جاوااسکریپت — مدرنترین روش برنامهنویسی async
اسکوپ و کلوژر در جاوااسکریپت — Callback و Closure رابطه نزدیکی دارند

