مدیریت بازگشتیهای ناهمگام در JavaScript: از Callback تا Async/Await
یکی از مثالهای بارز عملیات ناهمگام، AJAX (Asynchronous JavaScript and XML) است که به ما امکان میدهد بدون بارگذاری مجدد کل صفحه، اطلاعات را با سرور تبادل کنیم.1 فرض کنید تابعی داریم به نام InsertMessage() که وظیفه ارسال یک پیام به سرور را از طریق AJAX بر عهده دارد. انتظار داریم این تابع پس از اتمام عملیات، وضعیتی (مانند موفقیت یا عدم موفقیت) را بازگرداند. اما به دلیل ماهیت ناهمگام AJAX، هرگونه تلاش برای بازگرداندن مستقیم یک مقدار از داخل تابع success یا error کالبک AJAX به تابع InsertMessage()، به نتیجه دلخواه نمیرسد. به عبارت دیگر، عباراتی مانند return response.isSuccess; یا return false; در داخل کالبکهای success/error، بر مقدار بازگشتی تابع InsertMessage() تأثیری نخواهند گذاشت. دلیل آن ساده است: تابع InsertMessage() قبل از اینکه پاسخ AJAX دریافت شود و کالبکهای success/error فراخوانی شوند، به اتمام رسیده و مقدار undefined را برمیگرداند.
برای غلبه بر این چالش و مدیریت صحیح مقادیر بازگشتی از عملیات ناهمگام AJAX، سه رویکرد اصلی وجود دارد که در ادامه به تفصیل به آنها میپردازیم:
۱. استفاده از Promise (رویکرد مدرن و توصیه شده)
Promise یا قول، یک شیء در جاوااسکریپت است که نشاندهنده تکمیل یا شکست نهایی یک عملیات ناهمگام است.2 Promise ها راهی ساختاریافتهتر و خواناتر برای مدیریت عملیات ناهمگام نسبت به کالبکهای تودرتو (Callback Hell) ارائه میدهند.3 یک Promise میتواند در یکی از سه حالت باشد:
-
pending (در انتظار): حالت اولیه، نه تکمیل شده و نه رد شده است.4
fulfilled (تکمیل شده): به این معنی که عملیات با موفقیت به پایان رسیده است.
-
rejected (رد شده): به این معنی که عملیات با خطا مواجه شده است.5
هنگامی که یک عملیات ناهمگام با موفقیت انجام میشود، Promise با استفاده از متد resolve() مقدار موفقیتآمیز را برمیگرداند. در صورت بروز خطا، از متد reject() برای برگرداندن خطا استفاده میشود.6
پیادهسازی با Promise:
برای پیادهسازی تابع InsertMessage() با استفاده از Promise، تابع را به گونهای تعریف میکنیم که یک Promise جدید را بازگرداند:
function InsertMessage(currentUsername, friendUsername, message) {
return new Promise(function (resolve, reject) {
$.ajax({
url: "/user/PostMessage",
type: "POST",
data: {
SenderUsername: currentUsername,
RecieverUsername: friendUsername,
Message: message,
},
success: function (response) {
// در صورت موفقیت، Promise را با مقدار response.isSuccess حل میکنیم
resolve(response.isSuccess);
},
error: function () {
// در صورت خطا، Promise را با false حل میکنیم (یا با reject(error) رد میکنیم)
resolve(false);
}
});
});
}
در این ساختار، Promise به ما این امکان را میدهد که وضعیت نهایی عملیات AJAX را به تابع فراخوانی کننده منتقل کنیم. تابع resolve() برای نشان دادن موفقیت و reject() (که در اینجا به جای آن resolve(false) استفاده شده است) برای نشان دادن شکست استفاده میشود.
نحوه فراخوانی و استفاده:
برای استفاده از تابع InsertMessage() که یک Promise برمیگرداند، از متد .then() استفاده میکنیم.7 متد .then() دو کالبک میگیرد: یکی برای زمانی که Promise حل میشود (fulfilled) و دیگری برای زمانی که Promise رد میشود (rejected).
InsertMessage("Ali", "Reza", "Hello").then(function(result) {
if (result) {
console.log("Message sent successfully.");
} else {
console.log("Message failed.");
}
});
این رویکرد به وضوح نشان میدهد که چگونه میتوانیم کد را برای انجام اقدامات پس از تکمیل عملیات ناهمگام ساختار دهیم. Promise ها، با ایجاد یک زنجیره از عملیات، از پیچیدگی کالبکهای تودرتو جلوگیری کرده و کد را خواناتر و قابل نگهداریتر میکنند.8
۲. استفاده از Async/Await (رویکرد مدرن و تمیزتر - ES2017+)
Async/Await یک افزونه نحوی بر روی Promise ها است که در ES2017 (ECMAScript 2017) معرفی شد.9 این ویژگی به ما اجازه میدهد تا کدهای ناهمگام را به گونهای بنویسیم که شبیه به کدهای همگام به نظر برسند و از پیچیدگیهای then() و catch() که در Promise ها وجود دارد، بکاهیم.
-
async: یک تابع را به عنوان تابع ناهمگام علامتگذاری میکند.10 توابع async همیشه یک Promise را باز میگردانند.11
-
await: فقط میتواند در داخل توابع async استفاده شود.12 اجرای تابع را تا زمانی که Promise مرتبط با await حل یا رد شود، متوقف میکند.13 سپس مقدار حل شده Promise را برمیگرداند
پیادهسازی با Async/Await:
برای استفاده از async/await، تابع InsertMessage() را به یک تابع async تبدیل میکنیم و از await برای انتظار کشیدن برای تکمیل درخواست AJAX استفاده میکنیم:
async function InsertMessage(currentUsername, friendUsername, message) {
try {
// await تا زمانی که درخواست AJAX تکمیل شود، صبر میکند
const response = await $.ajax({
url: "/user/PostMessage",
type: "POST",
data: {
SenderUsername: currentUsername,
RecieverUsername: friendUsername,
Message: message,
}
});
// مقدار isSuccess را پس از دریافت پاسخ برمیگرداند
return response.isSuccess;
} catch (error) {
// در صورت بروز خطا در درخواست AJAX، false را برمیگرداند
return false;
}
}
در این پیادهسازی، کلمه کلیدی await باعث میشود که اجرای تابع InsertMessage تا زمانی که درخواست $.ajax تکمیل شود، متوقف شود. هنگامی که درخواست کامل شد، نتیجه یا خطا به ترتیب به متغیر response اختصاص داده میشود یا به بلوک catch منتقل میشود. این رویکرد کد را بسیار تمیزتر و خواناتر میکند، زیرا جریان کنترل به صورت خطی به نظر میرسد.
نحوه فراخوانی و استفاده:
برای فراخوانی یک تابع async، میتوانیم آن را در یک تابع async دیگر فراخوانی کنیم یا از یک IIFE (Immediately Invoked Function Expression) ناهمگام استفاده کنیم:
(async () => {
const result = await InsertMessage("Ali", "Reza", "Hello");
if (result) {
console.log("✅ Message sent.");
} else {
console.log("❌ Message failed.");
}
})();
این روش، بخصوص برای سناریوهایی که چندین عملیات ناهمگام به صورت متوالی اجرا میشوند، بسیار قدرتمند است و به توسعهدهندگان اجازه میدهد تا با پیچیدگی کمتری به مدیریت عملیات ناهمگام بپردازند.
۳. اجتناب از بازگرداندن مستقیم مقدار (Don't do this)
همانطور که قبلاً ذکر شد، تلاش برای بازگرداندن مستقیم یک مقدار از داخل کالبکهای ناهمگام به تابع خارجی، کارساز نیست و باید از این رویکرد اجتناب کرد.
let result = InsertMessage(...); // این به درستی کار نخواهد کرد
if (result) { /* ... */ }
در این مثال، InsertMessage() قبل از اینکه درخواست AJAX تکمیل شود، اجرا شده و undefined را به result اختصاص میدهد. بنابراین، شرط if (result) همیشه به صورت غیرمنتظره عمل خواهد کرد. این روش، منجر به کدهای باگدار و غیرقابل پیشبینی میشود و نباید مورد استفاده قرار گیرد.
انتخاب رویکرد مناسب
انتخاب بین Promise و Async/Await عمدتاً به ترجیحات شخصی و سازگاری با نسخههای جاوااسکریپت مورد استفاده بستگی دارد:
-
Promise: اگر با نسخههای قدیمیتر جاوااسکریپت کار میکنید یا نیاز به کنترل دقیقتر بر روی جریان Promise (مانند Promise.all یا Promise.race) دارید، Promise ها یک گزینه عالی هستند. آنها پایه و اساس async/await هستند و درک آنها برای درک عمیقتر مفاهیم ناهمگام ضروری است.
-
Async/Await: برای پروژههای جدیدتر که از ES2017 به بعد پشتیبانی میکنند، async/await به شدت توصیه میشود. این رویکرد کدهای ناهمگام را بسیار خواناتر، شبیه به کد همگام و سادهتر برای اشکالزدایی میکند. این روش به خصوص برای سناریوهای پیچیده با چندین عملیات ناهمگام زنجیرهای، کارایی بالایی دارد.
هر دو رویکرد Promise و Async/Await، راهحلهای مدرن و استاندارد برای مدیریت عملیات ناهمگام در جاوااسکریپت هستند و جایگزین مناسبی برای الگوی کالبک هستند که میتواند منجر به کدهای پیچیده و دشوار برای نگهداری شود. با درک صحیح و بهکارگیری این الگوها، میتوانیم کدهای جاوااسکریپت قدرتمندتر، قابل اطمینانتر و خواناتری برای برنامههای وب توسعه دهیم.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.