54- آموزش جاوااسکریپت | Promises : جایگزین مدرن Callbackها
در این آموزش با مفهوم Promises در جاوااسکریپت آشنا میشویم. یاد میگیریم چگونه با جایگزینی Callbackها، عملیاتهای ناهمگام (Async) را مدیریت کنیم، از resolve و reject استفاده کرده و با .then و .catch خطاها و نتایج را به شکلی تمیزتر مدیریت کنیم. این مبحث، پیشنیازی ضروری برای کار با Fetch API است.
آشنایی با Promises در جاوااسکریپت (ES6)
در این بخش به سراغ Promises میرویم که از استاندارد ES6 به جاوااسکریپت اضافه شدهاند و روشی مدرنتر و جایگزین برای Callbackها جهت مدیریت عملیاتهای ناهمگام (Asynchronous) هستند.
دلیل نامگذاری «Promise» این است که در حین انجام یک پردازش غیرهمزمان، به ما «قول» میدهد که پس از پایان کار، نتیجه مشخصی را اعلام کند. برای دریافت و پردازش نتیجه یک Promise موفق، از متد .then() استفاده میکنیم.
برای درک عملی، از همان مثال پست 51 مربوط به Callback یعنی آرایه پستها و توابع createPost و getPosts استفاده میکنیم و ساختار Callback را به Promise تبدیل میکنیم.
دادههای اولیه فرضی
فرض کنید دادهها و تابع خواندن پستها به شکل زیر تعریف شدهاند:
const posts = [
{ title: 'Post One', body: 'This is post one' },
{ title: 'Post Two', body: 'This is post two' }
];
function getPosts() {
setTimeout(() => {
let output = '';
posts.forEach((post) => {
output += `<li>${post.title}</li>`;
});
document.body.innerHTML = output;
}, 1000);
}
گام اول: حذف Callback و تعریف Promise در createPost
در ساختار قبلی، createPost یک پارامتر callback دریافت میکرد و بعد از posts.push آن را صدا میزد.
اکنون:
- پارامتر
callbackرا از ورودی تابع حذف میکنیم. - از تابع مقدار
new Promiseراreturnمیکنیم. - سازنده Promise تابعی دریافت میکند که دو پارامتر به نامهای
resolveوrejectدارد:resolve: زمانی فراخوانی میشود که کار با موفقیت به پایان رسیده باشد.reject: زمانی فراخوانی میشود که خطایی رخ داده باشد.
function createPost(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
posts.push(post);
// به جای فراخوانی callback، تابع resolve را اجرا میکنیم
resolve();
}, 2000);
});
}
گام دوم: فراخوانی تابع با متد .then()
دیگر نیازی نیست تابع getPosts را به عنوان آرگومان به createPost بفرستیم؛ بلکه آن را به متد .then() زنجیره میکنیم:
createPost({ title: 'Post Three', body: 'This is post three' })
.then(getPosts);
نتیجه اجرا: بعد از ۲ ثانیه، پست سوم اضافه شده و بلافاصله تابع getPosts در .then() اجرا میشود و هر ۳ پست در صفحه چاپ میشوند.
گام سوم: مدیریت خطا با reject و متد .catch()
اگر در زمان عملیات سرور یا پایگاهداده مشکلی پیش بیاید، باید از reject استفاده کنیم. برای شبیهسازی خطا، یک متغیر شرطی به تابع اضافه میکنیم:
function createPost(post) {
return new Promise((resolve, reject) => {
setTimeout(() => {
posts.push(post);
const error = true; // شبیهسازی بروز خطا در سمت سرور
if (!error) {
resolve();
} else {
reject('Error: Something went wrong');
}
}, 2000);
});
}
حالا برای مدیریت خطای ارسالشده از سمت reject، متد .catch() را به انتهای زنجیره اضافه میکنیم:
createPost({ title: 'Post Three', body: 'This is post three' })
.then(getPosts)
.catch((err) => console.log(err));
نکته مهم درباره خطای Uncaught:
اگر در کدی reject اجرا شود ولی متد .catch() را برای گرفتن آن ننویسیم، مرورگر خطای زیر را چاپ میکند:
Uncaught (in promise) Error: Something went wrong
به همین دلیل استفاده از .catch() برای دریافت تمیز خطاها و نمایش پیام مناسب به کاربر ضروری است.
اگر مقدار error را به false تغییر دهیم، بخش resolve اجرا شده و لیست پستها مجدداً بدون مشکل لود میشود.
جمعبندی و چرایی یادگیری Promise در این مرحله
یادگیری ساختار Promise پیشنیاز مستقیم بخشهای بعدی است؛ چرا که ابزارهای مدرنتر ارتباط با شبکه مانند Fetch API، به جای سبک سنتی AJAX (یعنی XMLHttpRequest و رویدادهای onload)، مستقیماً خروجی را در قالب Promise برمیگردانند.