async/await در جاوا اسکریپت
یک سینتکس ویژه برای کار با promises به شیوه ای راحت تر وجود دارد، به نام «async/wait» که درک و استفاده از آن به طرز شگفت انگیزی آسان است. کلمات کلیدی async و await در ES2018 برای کاهش تکرارها و حل محدودیت "در شکستن زنجیره" در promise ها معرفی شد. اما تفاوت بین promises و async/await چیست؟ هنگامی که از async/await استفاده می شود، then/catch به ندرت استفاده می شود. چرا که در درون خود مقادیر به صورت مستقیم به promise مورد نظر برمیگردند و دیگر نیازی به نوشتن زنجیره ای برای آنها نیست.
ویژگی های کلی async/await
1- جدیدترین روش نوشتن کدهای غیرهمزمان در زبان برنامه نویسی جاوااسکریپت است.
2- غیرقابل انسداد است مانند promise و callback
3- async/await برای ساده سازی روند کار و نوشتن promise های زنجیره ای طراحی شده است.
4- توابع async یک promise را برمیگردانند. اگر تابع یک خطا را برگرداند promise رد شده و اگر تابع یک مقدار را برگرداند promise پذیرفته شده است.
Async/Await یک ویژگی پیش بینی شده ی جاوااسکریپت است که کار با توابع ناهمگام را بسیار لذت بخش تر و قابل فهم تر کرده است. همانطور که گفته شد Async/Await برمبنای Promise ها ساخته شده است و با تمام API های موجود که پایه ی آن ها Promise هاست سازگار است.
نام Async/Await از async و await تشکیل شده است این دو کلمه ی کلیدی به ما کمک می کند تا کد های ناهمگام را مرتب کنیم.
توابع ناهمگام (async functions)
توابع async توسط ES8 به جاوااسکریپت اضافه شده و برای راحت تر کردن مدیریت عملیات ناهمگام استفاده می شود. توابع async به طور نهفته از پرامیس ها استفاده می کنند. برای درک این که این توابع واقعا چه هستند و چگونه کار میکنند ابتدا باید promise ها را به خوبی درک کنیم که در مقاله قبلی آموزش زبان برنامه نویسی جاوا اسکریپت با موسسه آموزشی آتریا کامل توضیح داده شده است. نحوه استفاده از توابع async بسیار ساده است. برای استفاده از این ویژگی برای مدیریت یک عملیات ناهمگام، ابتدا از کلمه کلیدی async هنگام تعریف کردن یک تابع استفاده می کنیم. کلمه async رو همیشه ابتدای تعریف تابع می نویسیم، مانند زیر:
کلمه "async" قبل از یک تابع به معنای یک چیز ساده است: یک تابع همیشه یک promise را برمی گرداند. سایر مقادیر به صورت خودکار در یک promise حل شده قرار می گیرند.
به عنوان مثال، این تابع یک promise حل شده را با نتیجه 1 برمی گرداند. بیایید آن را آزمایش کنیم:
ما می توانیم صراحتاً یک promise را برگردانیم که همان خواهد بود:
بنابراین، async تضمین می کند که تابع یک promise را برمی گرداند، و غیر promise ها را در آن قرار می دهد. بسیار ساده، درست است؟ اما کلمه کلیدی دیگری وجود دارد، await، که فقط در داخل توابع async کار می کند، و بسیار جالب است.
await
await اجرای توایع async را متوقف می کند. زمانی که await بعد از یک فراخوانی promise قرار می گیرد بقیه ی کد هارا منتظر نگه می دارد تا promise تمام شود و یک مقدار برگرداند. توجه داشته باشید که await فقط با promise ها کار میکند نه با callback ها. همچنین await فقط می تواند داخل توابع async استفاده شود.
سینتکس await به صورت زیر است:
همانطور که گفته شد کلمه کلیدی await باعث می شود جاوا اسکریپت منتظر بماند تا این promise حل شود و نتیجه خود را برگرداند.
در اینجا یک مثال با یک promise نشان داده شده است که در 1 ثانیه حل می شود:
اجرای تابع در خط (*) "مکث" می کند و زمانی که promise حل شد از سر گرفته می شود و نتیجه به نتیجه آن تبدیل می شود. بنابراین کد بالا "done!" را در یک ثانیه نشان می دهد.
بیایید تاکید کنیم: await به معنای واقعی کلمه اجرای تابع را تا زمانی که promise حل شود به حالت تعلیق در می آورد و سپس با نتیجه promise آن را از سر می گیرد. این عمل هیچ هزینه ای برای منابع CPU ندارد، زیرا جاوا اسکریپت می تواند کارهای دیگری را در این بین انجام دهد: اجرای اسکریپت های دیگر، مدیریت event ها و غیره.
این فقط یک سینتکس زیباتر برای دریافت نتیجه promise نسبت به promise است. همچنین، خواندن و نوشتن آسان تر است.
نمی توان از await در توابع معمولی (regular functions) استفاده کرد.
اگر بخواهیم از await در یک تابع غیر همگام (non-async function) استفاده کنیم، یک خطای نحوی (syntax error) وجود خواهد داشت:
اگر فراموش کنیم که کلمه کلیدی async را قبل از یک تابع قرار دهیم، ممکن است این خطا را دریافت کنیم. همانطور که قبلا گفته شد، await فقط در داخل یک تابع همگام (async function) کار می کند.
مثال زیر را ببینید.
مرورگرهای مدرن به await سطح بالا (top-level await ) در ماژول ها اجازه می دهند.
در مرورگرهای مدرن، زمانی که ما در یک ماژول هستیم، await در سطح بالا به خوبی کار می کند. برای مثال:
اگر از ماژول ها استفاده نمی کنیم، یا مرورگرهای قدیمی باید پشتیبانی شوند، یک دستور العمل جهانی وجود دارد: قرار دادن در یک تابع ناهمگام بی نام (anonymous async function)
مثل این:
Await ، “thenables” را می پذیرد.
مانند promise.then،await به ما اجازه می دهد تا از اشیاء thenable (آنهایی که متد then قابل فراخوانی دارند) استفاده کنیم. ایده این است که یک شی third-party ممکن است یک promise نباشد، اما با promise سازگار باشد: برای استفاده از آن با await کافی است که از .then پشتیبانی کند.
در اینجا یک کلاس Thenable دمو وجود دارد. await زیر نمونه های خود را می پذیرد:
اگر await یک شی non-promise با .then دریافت کند، آن متد را فراخوانی می کند که توابع built-in را به عنوان آرگومان ها حل و رد می کند (همانطور که برای یک اجراکننده Promise معمولی انجام می شود). سپس await منتظر می ماند تا یکی از آنها فراخوانی شود (در مثال بالا در خط (*) اتفاق می افتد) و سپس با نتیجه ادامه می یابد.
متدهای کلاس Async
برای اعلان یک متد کلاس async، کافی است async را به آن اضافه کنید:
معنی و مفهوم یکسان است: تضمین می کند که مقدار بازگشتی یک promise است و await را فعال می کند.
کنترل خطا
اگر یک promise به طور معمولی حل شود، پس از آن await promise، نتیجه را برمی گرداند. اما در حالت rejection، خطا می دهد، درست مثل اینکه یک عبارت پرتاب در آن خط وجود داشته باشد. کد زیر را ببینید.
که با آنچه که در ادامه نشان داده شده است یکسان است:
در شرایط واقعی، promise ممکن است مدتی طول بکشد تا رد شود. در این صورت یک تاخیر قبل از ایجاد خطا توسط await وجود خواهد داشت.
می توانیم آن خطا را با استفاده از try..catch، به همان روشی که به صورت پرتاب معمولی انجام می دهیم، بگیریم:
در صورت بروز خطا، کنترل به بلوک catch می پرد. ما همچنین می توانیم چندین خط را بپیچیم:
اگر try..catch نداشته باشیم، promise ایجاد شده با فراخوانی تابع async به نام ()f رد می شود. می توانیم catch. را برای مدیریت آن اضافه کنیم:
اگر فراموش کنیم catch. را در آنجا اضافه کنیم، خطای promise unhandled دریافت می کنیم (قابل مشاهده در کنسول). ما می توانیم چنین خطاهایی را با استفاده از کنترل کننده رویداد جهانی unhandledrejection کشف کنیم.
async/wait و promise.then/catch
وقتی از async/wait استفاده می کنیم، به ندرت به then. نیاز داریم، زیرا await انتظار را برای ما کنترل می کند. و می توانیم به جای catch. از یک try..catch معمولی استفاده کنیم. این معمولا (اما نه همیشه) راحت تر است.
اما در سطح بالای کد، وقتی خارج از هر تابع همگام سازی (async function) هستیم، از لحاظ سینتکسی نمی توانیم از await استفاده کنیم، بنابراین اضافه کردن then/catch. برای مدیریت نتیجه نهایی یا خطای falling-through، یک تمرین نرمال است. مانند آنچه که در خط (*) مثال بالا نشان داده شده است.
async/await با Promise.all به خوبی کار می کند.
هنگامی که باید منتظر چندین promise باشیم، می توانیم آنها را در Promise.all بپیچیم و سپس منتظر بمانیم:
در صورت بروز خطا، طبق معمول، از promise ناموفق به Promise.all منتشر می شود، و سپس تبدیل به یک استثنا می شود که می توانیم آن را با استفاده از try..catch در اطراف فراخوانی بگیریم.
خلاصه و چکیده مطالب async/await در زبان برنامه نویسی جاوا اسکریپت:
کلمه کلیدی async قبل از یک تابع دو اثر و نتیجه دارد:
1- باعث می شود که همیشه یک promise را برگرداند.
2- اجازه می دهد تا await در آن استفاده شود.
کلمه کلیدی await قبل از یک promise باعث می شود جاوا اسکریپت منتظر بماند تا آن promise حل شود و سپس:
1- اگر خطا باشد، یک استثنا ایجاد می شود - مثل اینکه خطای پرتاب در همان مکان فراخوانی شود.
2- در غیر این صورت، نتیجه را برمی گرداند.
آنها با هم یک چارچوب عالی برای نوشتن کدهای غیرهمزمان (asynchronous) ارائه می دهند که خواندن و نوشتن آن آسان است.
با async/await به ندرت نیاز به نوشتن promise.then/catch داریم، اما هنوز نباید فراموش کنیم که آنها بر پایه و اساس promise ها هستند، زیرا گاهی اوقات (مثلاً در بیرونی ترین محدوده) ما باید از این متدها استفاده کنیم. همچنین Promise.all زمانی خوب است که ما به طور همزمان منتظر چندین تسک هستیم.
در ادامه چند تمرین به همراه پاسخ انها جهت فهم بیشتر نشان شده است.
بازنویسی با استفاده از async/wait
کد زیر را با استفاده از async/await به جای then/catch. بازنویسی کنید:
نکات مهم در زیر کد آمده است:
نکات مهم:
1- تابع loadJson غیر همگام (async) می شود.
2- همه then. ها با await جایگزین می شوند.
3- ما می توانیم به جای اینکه منتظر ()answer.json باشیم، آن را برگردانیم، مانند زیر:
سپس کد بیرونی باید منتظر باشد تا آن promise حل شود. در case ما این مهم نیست.
4- خطای پرتاب شده از loadJson از طریق catch. کنترل می شود. ما نمی توانیم از (…)await loadJson در آنجا استفاده کنیم، زیرا در یک تابع async نیستیم.
فراخوانی غیرهمگام از غیر همگام (async from non-async)
ما یک تابع "regular" به نام f داریم. چگونه می توان تابع async به نام ()wait را فراخوانی کرد و از نتیجه آن در داخل f استفاده کرد؟
P.S. این کار از نظر فنی بسیار ساده است، اما این سوال برای توسعه دهندگانی که تازه با async/wait آشنا شده اند بسیار رایج است.
راه حل
فراخوانی async را به عنوان یک promise در نظر بگیرید و سپس به آن then. وصل کنید: