بررسی مفهوم async و await در جاوا اسکریپت


شاید شما هم با مفهوم async و await در جاوا اسکریپت آشنا نباشید و ندانید که این ویژگی‌های برای چه به جاوا اسکریپت اضافه شدند. همان‌طور که می‌دانید جاوا اسکریپت یک زبان تک رشته‌ای است و امکان چندوظیفه‌ای در این زبان وجود ندارد؛ پس وظایف مختلف در این زبان نمی‌توانند به صورت همزمان اجرا شوند. در نتیجه باید از راهکارهای موجود در این زبان برای همگام سازی وظایف استفاده کرد. در این مطلب به طور کامل مفهوم async و await در جاوا اسکریپت را بررسی میکنیم.

مفهوم Async و await در جاوا اسکریپت

همان‌طور که گفته شد در جاوا اسکریپت multitask وجود ندارد؛ بنابراین توابع باید پشت سر هم اجرا شوند. فرآیند‌های مختلفی وجود دارند که اجرای آن‌ها منوط به اجرای فرآیندهای قبلی است؛ مثلا برای محاسبه‌ی میانگین چند عدد ابتدا باید این اعداد با هم جمع شوند و خروجی آن بر تعداد اعداد تقسیم شود. برای میانگین گیری شما نمی‌توانید ابتدا عمل تقسیم را انجام دهید و سپس اعداد را با هم جمع کنید؛ چون به خروجی جمع برای تقسیم نیاز خواهید داشت.

کاری که باید انجام شود این است که توابع با هم همگام شوند و ترتیب اجرای آن‌ها مشخص شود. این دقیقا همان کاری است که افزونه‌ی Async و await در جاوا اسکریپت برای ما انجام می‌دهند. این افزونه بر اساس promise ها کار می‌کند. پس بهتر است قبل از پرداختن به مفهوم async و await در جاوا اسکریپت، promise ها را معرفی کنیم.

مفهوم Promise در جاوا اسکریپت

Promise در جاوا اسکریپت به معنی عملیات غیرمتقارن است؛ یعنی عملیاتی که برای اجرا باید منتظر اجرای عملیات دیگری باشند. در واقع پرامیس یک شی نگهدارنده است که تابعی را به عنوان ورودی دریافت کرده و پس از اتمام اجرای آن، با یک تابع callback، فراخوانی می‌شود. یک پرامیس می‌توانید در یکی از سه وضعیت قرار بگیرد:

Pending: وضعیت انتظار نیز گفته می‌شود و زمانی است که کدهای تابع هنوز به طور کامل اجرا نشده‌اند.

Fulfilled: وضعیتی است که کدها به صورت کامل اجرا شده‌ و تابع تکمیل شده است.

Rejected: در این وضعیت اجرای تابع بنا به دلایلی با شکست مواجه شده و تابع اجرا نشده است.

کد زیر نمونه‌ای از ساخت یک پرامیس در جاوا اسکریپت را نشان می‌‌دهد

const p = new Promise(function(resolve , reject){

// کدهای تابع که معمولاً به صورت آسنکرون اجرا می‌شوند

if(success){

resolve(value);

}else{

reject(error);

}

});

در اینجا پرامیس از نوع شی تعریف شده که آرگومان ورودی آن یک تابع است. تابع function خود دو پارامتر ورودی با نام‌های resolve و reject دارد که هر کدام وضعیت‌های زیر را نشان می‌دهند:

Resolve: اجرای موفقیت آمیز عملیات آسنکرون که به آن fulfilled نیز گفته می‌شود.

Reject: شکست عملیات آسنکرون

اگر تابع در هر کدام از دو وضعیت بالا قرار گرفته باشد، اجرای متوقف شده و در نتیجه عمر پرامیس تعریف شده برای آن نیز به پایان می‌رسد.

async در جاوا اسکریپت

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 در جاوا اسکریپت

به بیان ساده 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 در جاوا اسکریپت

برای درک بهتر مفهوم 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

مدیریت ارورها یکی دیگر از قابلیت‌های افزونه‌ی 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 اجازه می‌دهد تا از آن استفاده کنید.

از سوی دیگر await قبل از اجرای کامل پرامیس به کدهای جاوا اسکریپت ایست می‌دهد تا مطمئن شود که:

اگر خطایی رخ داد، یک exception ایجاد شود.

و اگر پرامیس کامل شد مقدار خروجی را برگرداند.

با کمک این دو افزونه ما به ندرت نیاز به نوشتن زنجیره‌ی پرامیس‌های پشت سر هم خواهیم داشت؛ ولی فراموش نکنید که این دو مفهوم نیز از پرامیس استفاده می‌کنند و گاهی ممکن است ناچار به استفاده از این زنجیره‌ها شویم.

منبع:

https://sabzlearn.ir/the-concept-of-async-and-await-in-javascript/