” جاوااسکریپت به صورت پیشفرض همزمان (Synchronous) عمل میکنه، اما برای کارهای زمانبر مثل دریافت داده از سرور، از عملیات غیرهمزمان (Asynchronous) استفاده میکنیم. این مقاله Callback، Promise و async/await را با مثالهای ساده توضیح میده. “
یکی از موضوعاتی که موقع کار با جاوااسکریپت حتماً باهاش مواجه میشیم، نحوه مدیریت عملیاتهای غیرهمزمان (Asynchronous) هست. این عملیاتها کارهایی هستن که زمانبر هستن و نمیتونیم منتظر تموم شدنشون بمونیم تا بقیه کد اجرا بشن.
بیاین با یه مثال روزمره شروع کنیم: فرض کنید میخواید غذای موردعلاقتون رو از رستوران سفارش بدید:
همزمان (Synchronous): شما جلوی رستوران وایمیستید و منتظر میمونید تا غذا آماده بشه، تحویل بگیرید، بعد برید خونه. اینطوری کلی وقت تلف شده!
غیرهمزمان (Asynchronous): شما سفارش میدید، شماره میگیرید و میرید خرید کنید یا کارهای دیگهتون رو انجام بدید. وقتی غذا آماده شد، بهتون خبر میدن :)
جاوااسکریپت هم دقیقاً همین شکلی کار میکنه! بعضی کارها مثل:
گرفتن اطلاعات از سرور (API)
خواندن فایل
تایمر و زمانبندیها
همه اینها زمانبر هستن و نمیتونیم کل برنامه رو متوقف کنیم تا تموم بشن.
اولین روشی که برای حل این مشکل ابداع شد، Callback بود. کلمه Callback یعنی "تماس برگشتی" - یعنی یه تابع رو پاس میدیم که وقتی کار تمام شد، اون تابع رو صدا بزنه.
// یه تابع ساده برای گرفتن اطلاعات کاربر
function getUserData(userId, callback) {
console.log('در حال دریافت اطلاعات کاربر...');
// شبیهسازی عملیات زمانبر
setTimeout(() => {
const user = {
id: userId,
name: 'داریوش',
age: 25
};
callback(user); // وقتی اطلاعات آماده شد، تابع callback رو صدا میزنیم
}, 2000); // 2 ثانیه تأخیر
}
// استفاده از تابع
getUserData(123, function(user) {
console.log('اطلاعات کاربر دریافت شد:', user);
});
console.log('این خط زودتر اجرا میشه!');در حال دریافت اطلاعات کاربر...
این خط زودتر اجرا میشه!
(بعد از 2 ثانیه) اطلاعات کاربر دریافت شد: {id: 123, name: 'داریوش', age: 25}همه چی تا اینجا خوب به نظر میرسه، اما مشکل کجاست؟ وقتی چندتا عملیات غیرهمزمان داریم که باید پشت سر هم اجرا بشن:
// فرض کنید میخوایم:
// 1. اول کاربر رو بگیریم
// 2. سپس پستهاش رو بگیریم
// 3. سپس کامنتهای اولین پست رو بگیریم
function getUser(userId, callback) {
setTimeout(() => {
callback({ id: userId, name: 'سامان' });
}, 1000);
}
function getPosts(userId, callback) {
setTimeout(() => {
callback([{ id: 1, title: 'پست اول' }, { id: 2, title: 'پست دوم' }]);
}, 1000);
}
function getComments(postId, callback) {
setTimeout(() => {
callback(['کامنت 1', 'کامنت 2']);
}, 1000);
}
// حالا چطور اینها رو پشت سر هم اجرا کنیم؟
getUser(123, function(user) {
console.log('کاربر:', user);
getPosts(user.id, function(posts) {
console.log('پستها:', posts);
getComments(posts[0].id, function(comments) {
console.log('کامنتها:', comments);
// و اگر بیشتر هم بشه...
// getLikes(comments[0].id, function(likes) { ... })
});
});
});این کد چندتا مشکل داره:
خوانایی پایین: وقتی چند عملیات غیرهمزمان پشت سر هم باشن، کد گیجکننده و شلوغ میشه.
مدیریت خطا سخت: باید برای هر Callback خطا رو جدا بررسی کنیم.
کنترل سخت: وقتی بخوایم چند عملیات همزمان انجام بدیم و منتظر همهشون بمونیم، کار پیچیده میشه.
برای حل مشکلات Callback، در ES6 (سال 2015) مفهوم Promise (قول) معرفی شد.
Promise یه شیء (Object) هست که نمایندهی یه عملیات غیرهمزمانه و به ما قول میده که در آینده یا نتیجهی عملیات رو بهمون میده یا دلیل شکستش رو.
در انتظار (Pending): عملیات هنوز تموم نشده
موفق (Fulfilled): عملیات با موفقیت تموم شد
رد شده (Rejected): عملیات با شکست مواجه شد
const myPromise = new Promise((resolve, reject) => {
// اینجا عملیات زمانبرمون رو انجام میدیم
const success = true; // فرض کنید این نتیجه عملیات مون هست
if (success) {
resolve('عملیات موفق بود!'); // اگر موفق بود
} else {
reject('عملیات ناموفق بود!'); // اگر شکست خورد
}
});function getUserData(userId) {
return new Promise((resolve, reject) => {
console.log('در حال دریافت اطلاعات کاربر...');
setTimeout(() => {
const success = Math.random() > 0.2; // 80% احتمال موفقیت
if (success) {
const user = {
id: userId,
name: 'سارا',
age: 30
};
resolve(user); // موفق
} else {
reject('خطا در دریافت اطلاعات کاربر'); // شکست
}
}, 2000);
});
}
// استفاده
getUserData(456)
.then((user) => {
console.log('اطلاعات دریافت شد:', user);
return user.name; // میتونیم مقدار رو به then بعدی پاس بدیم
})
.then((userName) => {
console.log('نام کاربر:', userName);
})
.catch((error) => {
console.error('خطا:', error);
})
.finally(() => {
console.log('عملیات تموم شد (چه موفق چه ناموفق)');
});حالا بیایم مشکل قبلی (دریافت کاربر → پستها → کامنتها) رو با Promise حل کنیم:
function getUser(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: 'سامان' });
}, 1000);
});
}
function getPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, title: 'پست اول' }, { id: 2, title: 'پست دوم' }]);
}, 1000);
});
}
function getComments(postId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['کامنت 1', 'کامنت 2']);
}, 1000);
});
}
// حالا به راحتی و زیبایی:
getUser(123)
.then(user => {
console.log('کاربر:', user);
return getPosts(user.id); // به مرحله بعد پاس میده
})
.then(posts => {
console.log('پستها:', posts);
return getComments(posts[0].id); // به مرحله بعد پاس میده
})
.then(comments => {
console.log('کامنتها:', comments);
})
.catch(error => {
console.error('خطا:', error); // فقط یه بار خطا رو مدیریت میکنیم
});// فرض کنید میخوایم اطلاعات چند کاربر رو همزمان بگیریم
const promise1 = getUser(1);
const promise2 = getUser(2);
const promise3 = getUser(3);
// منتظر میمونیم تا همهشون تموم بشن
Promise.all([promise1, promise2, promise3])
.then(users => {
console.log('همه کاربران:', users);
})
.catch(error => {
console.error('یکی از درخواستها شکست خورد:', error);
});
// یا اگر بخوایم اولی که تموم شد رو بگیریم
Promise.race([promise1, promise2, promise3])
.then(firstUser => {
console.log('اولین کاربر:', firstUser);
});اگر بخوایم بدون در نظر گرفتن موفقیت یا شکست، نتایج همه رو بگیریم:
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index + 1}: موفق`, result.value);
} else {
console.log(`Promise ${index + 1}: شکست`, result.reason);
}
});
});با اینکه Promise خیلی بهتر از Callback بود، اما بازم برای بعضیها سینتکس .then() و .catch() کمی غیرطبیعی به نظر میرسید. مخصوصاً وقتی چند مرحله از عملیات غیرهمزمان پشت سر هم اجرا میشدن..
در ES2017، دو کلمه کلیدی جدید معرفی شد: async و await که کارو خیلی سادهتر کردن!
تابعی که با async تعریف بشه، همیشه یه Promise برمیگردونه:
async function sayHello() {
return 'سلام';
}
// معادل:
function sayHello() {
return Promise.resolve('سلام');
}await فقط داخل توابع async کار میکنه و منتظر میمونه تا Promise resolve بشه:
async function getUserData() {
console.log('شروع دریافت اطلاعات...');
// منتظر میمونیم تا Promise resolve بشه
const user = await getUser(123);
console.log('کاربر:', user);
const posts = await getPosts(user.id);
console.log('پستها:', posts);
const comments = await getComments(posts[0].id);
console.log('کامنتها:', comments);
return comments; // این تابع خودش یه Promise برمیگردونه
}
// استفاده
getUserData()
.then(comments => {
console.log('نتیجه نهایی:', comments);
})
.catch(error => {
console.error('خطا:', error);
});یکی از ویژگیهای خوب async/await اینه که میتونیم از try/catch معمولی استفاده کنیم:
async function getUserDataSafe() {
try {
const user = await getUser(123);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
console.log('همه چی اوکیه!');
return { user, posts, comments };
} catch (error) {
console.error('یه خطایی رخ داد:', error);
// میتونیم خطا رو مدیریت کنیم یا دوباره throw کنیم
throw new Error(`خطا در دریافت اطلاعات: ${error}`);
} finally {
console.log('عملیات تموم شد');
}
}async function getAllData() {
// اینطوری نکنید (نوبتی اجرا میشه):
// const user = await getUser(1);
// const posts = await getPosts(2);
// 3 ثانیه طول میکشه
// اینطوری انجام بدید (همزمان):
const [user, posts] = await Promise.all([
getUser(1),
getPosts(2)
]);
// 2 ثانیه طول میکشه (همزمان اجرا میشن)
return { user, posts };
}async function getMultipleUsers(userIds) {
const users = [];
// اینطوری نکنید (نوبتی):
for (const id of userIds) {
const user = await getUser(id); // هر بار منتظر میمونه
users.push(user);
}
// اینطوری بهتره (همزمان):
const userPromises = userIds.map(id => getUser(id));
const users = await Promise.all(userPromises);
return users;
}// میتونیم از async/await با Promiseهای قدیمی هم استفاده کنیم
async function mixedExample() {
// با async/await
const user = await getUser(1);
// با Promise قدیمی
getPosts(user.id)
.then(posts => {
console.log('پستها:', posts);
})
.catch(error => {
console.error('خطا:', error);
});
return user;
}اگر تازهکار هستید: مستقیم برید سراغ async/await - سادهتر و قابلفهمتره
اگر با کد قدیمی کار میکنید: Promise رو خوب یاد بگیرید
اگر کد قدیمی دارید: فقط Callback رو بفهمید که بتونید کد قدیمی رو بخونید
بیایم یه مثال واقعی از فراخوانی API ببینیم:
async function fetchUserData() {
try {
console.log('در حال دریافت اطلاعات...');
// اطلاعات کاربر
const userResponse = await fetch('https://api.example.com/user/123');
if (!userResponse.ok) throw new Error('خطا در دریافت کاربر');
const user = await userResponse.json();
// پستهای کاربر (به صورت همزمان با اطلاعات اضافی)
const [postsResponse, profileResponse] = await Promise.all([
fetch(`https://api.example.com/user/${user.id}/posts`),
fetch(`https://api.example.com/user/${user.id}/profile`)
]);
if (!postsResponse.ok) throw new Error('خطا در دریافت پستها');
if (!profileResponse.ok) throw new Error('خطا در دریافت پروفایل');
const posts = await postsResponse.json();
const profile = await profileResponse.json();
// پردازش اطلاعات
const result = {
user: user,
postCount: posts.length,
lastPost: posts[0],
profile: profile
};
console.log('اطلاعات با موفقیت دریافت شد:', result);
return result;
} catch (error) {
console.error('خطا در دریافت اطلاعات:', error);
// میتونیم خطا رو به بخش دیگهای پاس بدیم
throw error;
} finally {
console.log('درخواست API تموم شد');
}
}
// استفاده
fetchUserData()
.then(data => {
// انجام کار با دادهها
updateUI(data);
})
.catch(error => {
// نمایش خطا به کاربر
showError(error.message);
});همیشه خطاها رو مدیریت کنید - چه با .catch() چه با try/catch
برای عملیات مستقل از Promise.all استفاده کنید - سرعت برنامه زیاد میشه
async/await جادو نمیکنه - فقط کد خواناتر میشه، پشت صحنه همون Promiseها هستن.
تابع async همیشه Promise برمیگردونه - حتی اگر return عادی داشته باشه
مدیریت عملیات غیرهمزمان در ابتدا ممکنه سخت به نظر برسه، اما با تمرین و درک صحیح از Callback → Promise → async/await، میتونید کدهای تمیز و خوانا بنویسید.
با async/await شروع کنید، درکش راحتتره. فقط یادتون نره که پشت صحنه دارید با Promise کار میکنید!
دیدگاه و یا پرسش خود را برای ما ارسال کنید.
هنوز دیدگاه یا پرسشی ایجاد نشده است :/
تجربهها، دیدگاهها و نکات الهامبخشی که با شما به اشتراک میگذاریم.