شاید شما هم با مفهوم async و await در جاوا اسکریپت آشنا نباشید و ندانید که این ویژگیهای برای چه به جاوا اسکریپت اضافه شدند. همانطور که میدانید جاوا اسکریپت یک زبان تک رشتهای است و امکان چندوظیفهای در این زبان وجود ندارد؛ پس وظایف مختلف در این زبان نمیتوانند به صورت همزمان اجرا شوند. در نتیجه باید از راهکارهای موجود در این زبان برای همگام سازی وظایف استفاده کرد. در این مطلب به طور کامل مفهوم async و await در جاوا اسکریپت را بررسی میکنیم.
همانطور که گفته شد در جاوا اسکریپت multitask وجود ندارد؛ بنابراین توابع باید پشت سر هم اجرا شوند. فرآیندهای مختلفی وجود دارند که اجرای آنها منوط به اجرای فرآیندهای قبلی است؛ مثلا برای محاسبهی میانگین چند عدد ابتدا باید این اعداد با هم جمع شوند و خروجی آن بر تعداد اعداد تقسیم شود. برای میانگین گیری شما نمیتوانید ابتدا عمل تقسیم را انجام دهید و سپس اعداد را با هم جمع کنید؛ چون به خروجی جمع برای تقسیم نیاز خواهید داشت.
کاری که باید انجام شود این است که توابع با هم همگام شوند و ترتیب اجرای آنها مشخص شود. این دقیقا همان کاری است که افزونهی Async و await در جاوا اسکریپت برای ما انجام میدهند. این افزونه بر اساس promise ها کار میکند. پس بهتر است قبل از پرداختن به مفهوم async و await در جاوا اسکریپت، promise ها را معرفی کنیم.
Promise در جاوا اسکریپت به معنی عملیات غیرمتقارن است؛ یعنی عملیاتی که برای اجرا باید منتظر اجرای عملیات دیگری باشند. در واقع پرامیس یک شی نگهدارنده است که تابعی را به عنوان ورودی دریافت کرده و پس از اتمام اجرای آن، با یک تابع callback، فراخوانی میشود. یک پرامیس میتوانید در یکی از سه وضعیت قرار بگیرد:
کد زیر نمونهای از ساخت یک پرامیس در جاوا اسکریپت را نشان میدهد
const p = new Promise(function(resolve , reject){
// کدهای تابع که معمولاً به صورت آسنکرون اجرا میشوند
if(success){
resolve(value);
}else{
reject(error);
}
});
در اینجا پرامیس از نوع شی تعریف شده که آرگومان ورودی آن یک تابع است. تابع function خود دو پارامتر ورودی با نامهای resolve و reject دارد که هر کدام وضعیتهای زیر را نشان میدهند:
اگر تابع در هر کدام از دو وضعیت بالا قرار گرفته باشد، اجرای متوقف شده و در نتیجه عمر پرامیس تعریف شده برای آن نیز به پایان میرسد.
async در جاوا اسکریپت برای توابع آسنکرون استفاده میشود. این ویژگی به صورت خودکار یک پرامیس برای تابع میسازد تا اجرای آن را مدیریت کند. نحوهی ساخت یک async به شکل زیر است:
async function f() {
return 1;
}
این دستور باید قبل از دستور فانکشن نوشته شود و یک معنی ساده دارد؛ تابع همیشه یک پرامیس برمیگرداند. در صورتی که پرامیس به وضعیت تکمیل یا همان resolve برود، تابع مقدار یک را برمیگرداند. این کد را در نظر بگیرید:
async function f() {
return 1;
}
f().then(alert); // 1
این کد را میتوان به این صورت نیز نوشت:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
بنابراین تابع async در جاوا اسکریپت تضمین میکند که یک پرامیس باید برگردانده شود؛ مگر اینکه اجرای آن با شکست مواجه شود. اما این تمام ماجرا نیست، دستور دیگری نیز وجود دارد که پس از آسینک اجرا میشود و Await نام دارد.
به بیان ساده Await در جاوا اسکریپت اجرای دستور Async را متوقف میکند. اگر Async نباشد، Await ای هم وجود نخواهد داشت. وقتی بعد از یک پرامیس از Await استفاده میشود، این دستور اجرای مابقی کدها را تا زمان تکمیل پرامیس متوقف میکند. میتواند گفت عملکرد دو مفهوم async و await در جاوا اسکریپت مکمل یکدیگرند. بد نیست بدانید تابع Await در جاوا اسکریپت فقط با پرومیسها کار میکند و کاری به callbackها ندارد. سینتکس تابع await در جاوا اسکریپت به صورت زیر است:
// works only inside async functions
let value = await promise;
در نمونهی زیر یک پرامیس داریم که در عرض یک ثانیه تکمیل میشود.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // wait until the promise resolves (*)
alert(result); // "done!"
}
f();
اجرای تابع بالا در خط کد (*) به حالت توقف میرود و زمانی مجددا اجرای کدها را ادامه میدهد که پرامیس به وضعیت resolve یا تکمیل رفته باشد. در نتیجه خروجی کد در یک ثانیه عبارت done! را نمایش خواهد داد. همانطور که گفته شد، await در جاوا اسکریپت، اجرای توابع را تا زمان تکمیل پرامیس متوقف میکند و بعد از کامل شدن تابع داخل پرامیس، اجرای کد را مجددا از سر میگیرد. اما، به این نکته توجه داشته باشید که این کار هیچگونه اتلاف cpu ندارد؛ چون موتور جاوا اسکریپت در این زمان میتوان به اجرای کارهای دیگری نظیر اجرای اسکریپتهای دیگر، رسیدگی به ایونتها و… بپردازد.
برای درک بهتر مفهوم async و await در جاوا اسکریپت یک مثال از کاربرد این افزونه در جاوا اسکریپت میزنیم تا شما بتوانید سادگی کار با این دو ویژگی را با ویژگیهای promise و callback مقایسه کنید. قطعه کد زیر نحوهی دریافت یک منبع JSON و تفکیک آن را نشان میدهد:
const getFirstUserData = () => {
return fetch('/users.json') // get users list
.then(response => response.json()) // parse JSON
.then(users => users[0]) // pick first user
.then(user => fetch(`/users/${user.name}`)) // get user data
.then(userResponse => userResponse.json()) // parse JSON
}
getFirstUserData()
کد زیر همان تابع را با استفاده از async await در جاوا اسکریپت بازنویسی میکند:
const getFirstUserData = async () => {
const response = await fetch('/users.json') // get users list
const users = await response.json() // parse JSON
const user = users[0] // pick first user
const userResponse = await fetch(`/users/${user.name}`) // get user data
const userData = await userResponse.json() // parse JSON
return userData
}
getFirstUserData()
همانطور که ملاحظه میکنید، استفاده از async await میتواند کدهای ما را سادهتر کند. نکتهی آخر از این مبحث اینکه توابع آسنکرون را میتوان به راحتی پشت سر هم زنجیر کرد و سینتکسشان نیز بسیار سادهتر از زنجیرههای پرامیس خواهد بود. به مثال زیر توجه کنید:
const promiseToDoSomething = () => {
return new Promise(resolve => {
setTimeout(() => resolve('I did something'), 10000)
})
}
const watchOverSomeoneDoingSomething = async () => {
const something = await promiseToDoSomething()
return something + '\nand I watched'
}
const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {
const something = await watchOverSomeoneDoingSomething()
return something + '\nand I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then(res => {
console.log(res)
})
مدیریت ارورها یکی دیگر از قابلیتهای افزونهی Async Await است که به شما این امکان را میدهد هر گونه خطایی را به داخل ساختار try…catch ارسال کنید. به عنوان مثال:
function thisThrows() {
throw new Error("Thrown from thisThrows()");
}
try {
thisThrows();
} catch (e) {
console.error(e);
} finally {
console.log('We do cleanup here');
}
// Output:
// Error: Thrown from thisThrows()
// ...stacktrace
// We do cleanup here
کد بالا یک دستور سادهی try… catch را نشان میدهد که در آن تابع thisThrows() یک خطا را throw کرده و ما آن را catch میکنیم و در بلوک finally یک کد اختیاری برای آن اجرا میکنیم. این تابع را میتوان با کمک مفهوم async و await در جاوا اسکریپت به شکل زیر پیاده سازی کرد:
async function thisThrows() {
throw new Error("Thrown from thisThrows()");
}
async function run() {
try {
await thisThrows();
} catch (e) {
console.error(e);
} finally {
console.log('We do cleanup here');
}
}
run();
// Output:
// Error: Thrown from thisThrows()
// ...stacktrace
// We do cleanup here
همانطور که ملاحظه میکنید، استفاده از async و awaitبرای اینکار کار پیاده سازی را راحتتر کرده و باعث خوانایی بیشتر و درک بهتر کد میشود.
دو مفهوم async و await در جاوا اسکریپت در کنار یکدیگر چارچوبی عالی برای نوشتن و اجرای کدهای آسنکرون یا غیرهمزمان ارائه میدهند و خواندن و نوشتنشان نیز بسیار ساده است. به طور خلاصه کلمه کلیدی async دو ویژگی اصلی دارد:
از سوی دیگر await قبل از اجرای کامل پرامیس به کدهای جاوا اسکریپت ایست میدهد تا مطمئن شود که:
با کمک این دو افزونه ما به ندرت نیاز به نوشتن زنجیرهی پرامیسهای پشت سر هم خواهیم داشت؛ ولی فراموش نکنید که این دو مفهوم نیز از پرامیس استفاده میکنند و گاهی ممکن است ناچار به استفاده از این زنجیرهها شویم.
منبع:
https://sabzlearn.ir/the-concept-of-async-and-await-in-javascript/