در دنیای سینمای همواره در حال تحول، یکی از عمیق‌ترین دگرگونی‌ها، تکامل جلوه‌های ویژه بوده است. فیلمسازان در طول سال‌ها، با بهره‌گیری از فناوری‌های پیشرفته، تصاویر شگفت‌انگیزی خلق کرده‌اند که مخاطبان سراسر جهان را مجذوب خود می‌کند. این مقاله به بررسی جذاب سفر جلوه‌های ویژه می‌پردازد، از روزهای اولیه جلوه‌های عملی تا دوران انقلابی تصویرسازی کامپیوتری (CGI). با ما همراه شوید تا جادوی پشت صحنه را کشف کنیم و ببینیم چگونه CGI صنعت فیلم را دگرگون کرده است.

فهرست مطالب
1. تولد جلوه‌های عملی
2. مینیاتورها و نقاشی‌های مات
3. انیمیشن استاپ‌موشن: یک نقطه عطف
4. ظهور گرافیک کامپیوتری
5. پارک ژوراسیک: یک نقطه عطف در CGI
6. تایتانیک: محو کردن مرزها
7. ماتریکس: بازتعریف واقعیت
8. آواتار: فراتر رفتن از محدودیت‌ها
9. دنیای سینمایی مارول: CGI ابرقهرمانی
10. ظهور ضبط حرکت
11. CGI در انیمیشن: انقلاب پیکسار
12. جوان‌سازی و بدل‌های دیجیتالی
13. آینده جلوه‌های ویژه
14. CGI در تلویزیون
15. CGI در بازی‌ها: یک رابطه هم‌افزا

1. تولد جلوه‌های عملی
پیش از دوران دیجیتال، فیلم‌ها برای خلق تصاویر خیره‌کننده به جلوه‌های عملی متکی بودند. تکنیک‌هایی مانند گریم، پروتز و عروسک‌بازی برای به تصویر کشیدن موجودات و دنیاهای فانتزی به کار می‌رفتند. فیلم‌های نمادینی مانند «کینگ کونگ» (1933) و «جادوگر شهر از» (1939) هنر اولیه جلوه‌های عملی را به نمایش گذاشتند.

2. مینیاتورها و نقاشی‌های مات
استفاده از مینیاتورها و نقاشی‌های مات به فیلمسازان اجازه می‌داد تا صحنه‌های پیچیده و در مقیاس بزرگ را خلق کنند. آثار کلاسیکی مانند «برباد رفته» (1939) و «2001: یک ادیسه فضایی» (1968) مهارت‌های موجود در خلق مناظر نفس‌گیر و نبردهای حماسی را به نمایش گذاشتند.

3. انیمیشن استاپ‌موشن: یک نقطه عطف
انیمیشن استاپ‌موشن بعد جدیدی به جلوه‌های ویژه معرفی کرد. پیشگامانی مانند ری هریهاوزن موجودات افسانه‌ای را در فیلم‌هایی مانند «جیسون و آرگونوت‌ها» (1963) به زندگی درآوردند. این تکنیک زمینه را برای تلاش‌های جاه‌طلبانه‌تر فراهم کرد.

4. ظهور گرافیک کامپیوتری
دهه‌های 70 و 80 میلادی شاهد تولد گرافیک کامپیوتری در سینما بودند. فیلم‌هایی مانند «ترون» (1982) نگاهی اجمالی به پتانسیل CGI ارائه دادند، هرچند در مقیاس محدود. اما این تازه شروع کار بود.

5. پارک ژوراسیک: یک نقطه عطف در CGI
در سال 1993، «پارک ژوراسیک» استیون اسپیلبرگ با دایناسورهای واقع‌گرایانه‌اش که به لطف CGI به وجود آمده بودند، مخاطبان را شگفت‌زده کرد. این فیلم لحظه سرنوشت‌سازی در تاریخ سینما را رقم زد و ثابت کرد که کامپیوترها می‌توانند موجودات زنده و متقاعدکننده‌ای خلق کنند.

6. تایتانیک: محو کردن مرزها
«تایتانیک» (1997) جیمز کامرون از CGI برای بازسازی کشتی بدشانس و غرق شدن غم‌انگیز آن استفاده کرد. این فیلم به طور یکپارچه جلوه‌های عملی را با جادوی دیجیتال ترکیب کرد و استانداردی جدید برای واقع‌گرایی در فیلم‌ها تعیین کرد.

7. ماتریکس: بازتعریف واقعیت
«ماتریکس» (1999) با استفاده نوآورانه از CGI، سکانس‌های اکشن را متحول کرد و جلوه نمادین «زمان گلوله» را معرفی کرد. این فیلم مرزهای آنچه از نظر داستان‌گویی بصری امکان‌پذیر بود را به چالش کشید.

8. آواتار: فراتر رفتن از محدودیت‌ها
جیمز کامرون در سال 2009 با «آواتار» بازگشت، فیلمی که مخاطبان را با استفاده از CGI پیشگامانه به دنیای پررونق پاندورا منتقل کرد. «آواتار» پتانسیل فناوری سه بعدی را نشان داد و استاندارد جدیدی برای داستان‌گویی فراگیر تعیین کرد.

9. دنیای سینمایی مارول: CGI ابرقهرمانی
دنیای سینمایی مارول، با شروع «مرد آهنی» (2008)، از CGI برای به تصویر کشیدن ابرقهرمانان و نبردهای حماسی آنها استفاده کرد. فیلم‌هایی مانند «انتقام‌جویان» (2012) پتانسیل داستان‌گویی به هم پیوسته مبتنی بر CGI را به نمایش گذاشتند.

10. ظهور ضبط حرکت
فناوری ضبط حرکت، که با فیلم‌هایی مانند «ارباب حلقه‌ها: دو برج» (2002) محبوب شد، به بازیگران اجازه می‌داد تا شخصیت‌های فانتزی را با واقع‌گرایی شگفت‌انگیزی به تصویر بکشند. بازی اندی سرکیس در نقش گولوم استانداردی جدید برای ضبط عملکرد تعیین کرد.

11. CGI در انیمیشن: انقلاب پیکسار
استودیو انیمیشن پیکسار با آثار کلاسیک CGI مانند «داستان اسباب‌بازی» (1995) انیمیشن را متحول کرد. فیلم‌های آنها فناوری پیشرفته را با داستان‌گویی دلنشین ترکیب می‌کردند و مخاطبان در هر سنی را مجذوب خود می‌کردند.

12. جوان‌سازی و بدل‌های دیجیتالی
در سال‌های اخیر، فناوری جوان‌سازی و بدل‌های دیجیتالی ظهور کرده است که به بازیگران اجازه می‌دهد نقش‌های نمادین خود را دوباره بازی کنند. فیلم‌هایی مانند «ایرلندی» (2019) پتانسیل این فناوری را نشان دادند.

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

14. CGI در تلویزیون
سریال‌های تلویزیونی مانند «بازی تاج و تخت» (2011–2019) از CGI برای خلق دنیاهای حماسی و نبردهای نفس‌گیر در یک صفحه نمایش کوچکتر استفاده کرده‌اند و مرز بین فیلم و تلویزیون را محو کرده‌اند.

15. CGI در بازی‌ها: یک رابطه هم‌افزا
بازی‌های ویدیویی به کانون نوآوری CGI تبدیل شده‌اند. عناوین مانند «آخرین ما قسمت دوم» (2020) داستان‌گویی سینمایی و تصاویر هم‌سطح با بلاک‌باسترهای هالیوود را به نمایش می‌گذارند.

در نتیجه، تکامل جلوه‌های ویژه در سینما، به ویژه ظهور CGI، نحوه روایت داستان‌ها روی پرده بزرگ را دگرگون کرده است. از روزهای اولیه جلوه‌های عملی تا امکانات بی‌نهایت فناوری امروز، جلوه‌های ویژه همچنان به گسترش مرزهای تخیل ادامه می‌دهند. با نگاه به آینده، می‌توانیم فقط تجربیات بصری خیره‌کننده‌ای را که در دنیای سینما در انتظار ما هستند، تصور کنیم.

پرسش‌های متداول (FAQs)

1. اولین فیلمی که به طور گسترده از CGI استفاده کرد چه بود؟

«ترون» (1982) اولین فیلمی بود که به طور گسترده از CGI استفاده کرد، اگرچه «جنگ ستارگان: قسمت چهارم - امید جدید» (1977) نیز دارای تصاویر اولیه تولید شده توسط کامپیوتر بود.

2. CGI چه تأثیری بر هزینه فیلم‌سازی داشته است؟

CGI هم هزینه‌ها را افزایش داده و هم کاهش داده است. در حالی که ایجاد آن می‌تواند پرهزینه باشد، اغلب نیاز به مجموعه‌های عملی پر زرق و برق را کاهش می‌دهد و می‌تواند در هزینه‌های بدلکاری‌های خطرناک صرفه‌جویی کند.

3. آیا محدودیت‌هایی در فناوری CGI وجود دارد؟

در حالی که CGI راه زیادی را طی کرده است، ایجاد شخصیت‌ها و حالات چهره واقعاً زنده انسان همچنان یک چالش است.

4. کدام فیلم مرزهای فناوری ضبط حرکت را به چالش کشید؟

«ارباب حلقه‌ها: دو برج» (2002) به دلیل استفاده پیشگامانه از ضبط حرکت، به ویژه در خلق گولوم، شناخته شده است.

5. برخی از پیشرفت‌های آینده در فناوری CGI چیست؟

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


شخصیت‌های فیلم‌ها و سریال‌های تلویزیونی بر اساس قوانین خاصی از فضا و زمان زندگی می‌کنند. وقتی کارگردان صحنه‌ای از دوران کودکی قهرمان را به مخاطب نشان می‌دهد، معمولاً بازیگری را انتخاب می‌کند که ظاهری مشابه داشته باشد. اما اگر یک شخصیت بزرگسال نیاز داشته باشد که همان‌طور که در جوانی بود، ظاهر شود چه؟ اینجاست که فناوری جوان‌سازی مبتنی بر هوش مصنوعی وارد عمل می‌شود.

بیایید ببینیم چگونه قهرمانان فیلم "به دوران جوانی بازگردانده" می‌شوند و تولیدکنندگان برای فیلم‌برداری یک صحنه کوتاه با چه مشکلاتی روبرو هستند.

فناوری جوان‌سازی چیست و محدودیت‌های آن کدامند؟

تا زمانی که فناوری جوان‌سازی به اندازه کافی توسعه نیافته بود، تمام بهترین تغییرات بازیگران توسط گریمورها ایجاد می‌شد. هنر گریم یکی از اولین فناوری‌هایی بود که امکان تغییر سن یک بازیگر را فراهم می‌کرد.

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

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

به طور خلاصه، جوان‌سازی یک فناوری جلوه‌های ویژه سه بعدی است که برای جوان‌تر نشان دادن یک بازیگر استفاده می‌شود. برای این کار، استودیوهای پس از تولید معمولاً یک تصویر دیجیتال را ویرایش می‌کنند یا لایه‌ها یا اصلاحات تولید شده توسط کامپیوتر (CGI) را روی صحنه‌های مورد نیاز اعمال می‌کنند.

وقتی بازیگران جوان‌سازی شده را می‌بینید، تقریباً همیشه انیمیشن‌های CGI را می‌بینید که بر اساس جلوه‌های بصری (VFX) ساخته شده‌اند. بازیگران با نشانگرهای کوچکی روی صورت خود فیلم‌برداری می‌شوند تا حرکات موضوع سه بعدی با حرکات سر بازیگر هماهنگ شود.

سپس یک نسخه سه بعدی نامرئی از سر ساخته می‌شود که با حرکات سر بازیگر هماهنگ می‌شود. روی این سر، ویرایشگر "وصله‌هایی" را از ابر نقاط قرار می‌دهد که به عنوان فیلتر برای پوست تصویر اصلی عمل می‌کنند. گاهی اوقات این "وصله‌ها" از فیلم‌برداری یک بدل جوان گرفته می‌شوند که سعی می‌کند حرکات بازیگر اصلی را تکرار کند، صحنه را در همان دکور و با همان نورپردازی طی کند.

سپس ویرایشگر یک "جراحی زیبایی" کامپیوتری انجام می‌دهد، چانه را اصلاح می‌کند، استخوان‌های گونه را برجسته‌تر می‌کند و گوش‌ها و بینی را که در طول زندگی بازیگر رشد کرده‌اند، کاهش می‌دهد.

برای دیدن نحوه کارکرد این فناوری در فیلم اخیر ویل اسمیت، Gemini Man، این ویدیو را تماشا کنید:

دو نمونه از جوان‌سازی VFX در تولید فیلم‌های هالیوود

استاندارد جوان‌سازی یک بازیگر اکنون صحنه‌ای از کورت راسل از فیلم Guardians of the Galaxy 2 در نظر گرفته می‌شود. شاید این اولین فیلم سینمایی بود که اثرات احیاکننده روی یک بازیگر به قدری خوب انجام شد که بیننده ناآگاه متوجه آن نشد. و همه اینها با استفاده از گریم و CGI حرفه‌ای امکان‌پذیر شد.

کمتر کسی می‌داند، اما فناوری جوان‌سازی اغلب با یک بدل ترکیب می‌شود تا شبیه به نسخه جوان‌سازی شده بازیگر باشد. این دقیقاً مورد کورت راسل بود. در اینجا کورت در کنار بدل جوانش، آرون شوارتز ایستاده است.

من همیشه رویای این را داشتم که #کورت‌راسل و من در کنار هم با ژاکت‌های یقه اسکی مشکی یکسان بایستیم و غروب خورشید را تماشا کنیم - با نقاطی روی صورتمان. ببینید بچه‌ها... رویاها به حقیقت می‌پیوندند 😬 #TBT #GOTGVOL2 #یونگهگو #بهترین‌ها
نوشته شده توسط آرون شوارتز در پنجشنبه، 25 مه 2017

ابتدا کورت راسل گریم شده صحنه را بازی کرد. سپس حرکات و حالات چهره او توسط آرون شوارتز تکرار شد. در مرحله بعد، پوست جوان او به صورت دیجیتالی "اعمال" شد به راسل. کل صحنه حدود دو ماه طول کشید تا تمام CGI فیلم پردازش شود.

نمونه برجسته دیگر از استفاده از این فناوری، فیلم اخیراً تحسین‌شده، The Irishman است. بازیگران اصلی، رابرت دنیرو و آل پاچینو، که به ترتیب 76 و 79 ساله هستند، در فیلم نقش مردانی 40 ساله را بازی کردند. بر خلاف فناوری مورد استفاده در Guardians of the Galaxy، دنیرو به روش دیگری جوان‌سازی شد.

بازیگر به صراحت از کار با کلاه و نشانگرهای ضبط خودداری کرد. بنابراین، به طور خاص برای این فیلم، تیم استودیو ILM برنامه خود را ساخت. این به سازندگان ویدیو اجازه داد تا حالات چهره بازیگر را از فیلم‌برداری بدون نشانگرهای قابل مشاهده "جدا" کنند.

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

از آنجایی که The Irishman از بدل‌های جوان استفاده نکرد، بسیاری خاطرنشان کردند که قهرمانان جوان‌سازی شده دنیرو و آل پاچینو با این وجود مانند مردان مسن حرکت می‌کردند.

می‌توانید نمونه‌های بیشتری از نحوه استفاده از این فناوری در سینما را در این ویدیو تماشا کنید:

فناوری جوان‌سازی و پیامدهای آن در صنعت فیلم

آیا چیزی در مورد فیلم آینده Finding Jack شنیده‌اید، جایی که جیمز دین معروف با استفاده از فناوری CGI مدرن "به زندگی بازگردانده" می‌شود؟

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

در عین حال، آنها از بدل برای این کار استفاده نخواهند کرد، بلکه صرفاً سبک بازیگری ستاره را به شخصیت جدید منتقل می‌کنند. اگر فیلم موفق شود چه اتفاقی می‌افتد؟

آیا این آغاز دوران "بازیگران جاودانه" خواهد بود، زمانی که آرنولد شوارتزنگر یا برد پیت می‌توانند برای همیشه روی صفحه نمایش بمانند، از طریق استفاده از انتقال حقوق استفاده از هویت خود به Warner Bros یا Disney؟ شاید. فقط زمان نشان خواهد داد.

در حال حاضر، فناوری جوان‌سازی هنوز نسبتاً گران است. فقط شرکت‌های بزرگ هالیوود می‌توانند از آن استفاده کنند. یک مدل سر دیجیتال حدود یک میلیون دلار هزینه دارد و هر فریم با انیمیشن آن، بسته به پیچیدگی، از 30 تا 100 هزار دلار متغیر است.

شاید این فناوری‌ها با گذشت زمان ارزان‌تر شوند، اما تا به امروز، به سختی برای تولید انبوه فیلم مقرون به صرفه هستند.

با این حال، با CGI مدرن و فناوری‌های جایگزینی دیالوگ اضافی (ADR)، آینده فیلم‌سازی که در آن تقریباً هر کسی می‌تواند بازیگر شود، دور نیست. و این سؤالات اخلاقی متعددی را ایجاد می‌کند.

اخلاق و آینده فیلم‌سازی

به عنوان سرویسی که امکان تقلید از گفتار هر فرد و تولید نامحدود محتوای صوتی بر اساس گفتار شخصی را فراهم می‌کند، Respeecher اخلاق هوش مصنوعی را جدی می‌گیرد. هر روز، مشتریان ما در هالیوود و سراسر جهان با فناوری کلونینگ صدا کار می‌کنند که به رویکرد اخلاقی خاصی نیاز دارد.

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

در بیشتر موارد، شبکه‌های عصبی مولد متخاصم (GAN) برای ایجاد چنین ویدیوهایی استفاده می‌شوند. یک قسمت از الگوریتم از عکس‌های واقعی یک شیء خاص یاد می‌گیرد. تصویری ایجاد می‌کند، به معنای واقعی کلمه با قسمت دوم الگوریتم "رقابت" می‌کند تا زمانی که شروع به اشتباه گرفتن کپی با اصلی کند.

روش‌های تشخیص حرکت و تبدیل آن به یک ویدیوی هدف که شبیه یک تصویر هدف باشد در سال 2016 معرفی شد. آنها امکان ایجاد حالات چهره جعلی را در یک ویدیوی 2 بعدی در زمان واقعی فراهم می‌کنند.

اگرچه این فناوری بیشتر برای اهداف خوب استفاده می‌شود، مواردی وجود دارد که دیپ‌فیک به طور مخرب برای جایگزینی برخی از عناصر تصویر با تصاویر مورد نظر استفاده شده است. در سال 2017، دیپ‌فیک هوش مصنوعی به ویژه برای ایجاد ویدیوهای پورنوگرافی جعلی با افراد مشهور یا برای انتقام پورنوگرافی استفاده شد.

بی‌گمان، شرکت‌هایی که به چنین فناوری‌هایی دسترسی دارند مسئول سفارشاتی هستند که انجام می‌دهند و با چه کسانی همکاری می‌کنند. پورنوگرافی ممکن است بزرگترین مشکل اجتماعی ایجاد شده توسط فناوری دیپ‌فیک نباشد. تصور کنید اگر سیاستمداران یا دولت‌های بی‌وجدان از چنین فناوری‌هایی برای ایجاد اخبار جعلی استفاده کنند.

با این حال، نمونه‌های زیادی از استفاده از این فناوری برای اهداف خوب وجود دارد. دیپ‌فیک در فضای موزه استفاده شده است. به عنوان مثال، در موزه سالوادور دالی در فلوریدا، نمایشگاه Dalí Lives برای بزرگداشت 115مین تولد هنرمند مشهور برگزار شد.

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

فناوری دیپ‌فیک امکانات متعددی را در حوزه آموزش فراهم می‌کند. محتوای تولید شده توسط هوش مصنوعی می‌تواند شخصیت‌های تاریخی را به کلاس درس بیاورد تا درس جذاب‌تری ارائه دهند.

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

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

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

امروزه، جامعه حرفه‌ای که توسط ارائه‌دهندگان این فناوری‌ها نمایندگی می‌شود، از ابزارهای خود محافظت می‌کند و با پیروی از دستورالعمل‌های اخلاقی دقیق کار می‌کند. مهم نیست چه کسی هستیم، ارائه‌دهندگان فناوری یا کاربران، مسئولیت ما این است که اطمینان حاصل کنیم که این فناوری‌ها به طور مسئولانه استفاده می‌شوند.

مدیر توسعه کسب و کار
تمرکز راستِم بر ایجاد روابط تجاری جدید و توسعه استراتژی‌هایی است که حضور در بازار را افزایش می‌دهد. تخصص او در توسعه کسب و کار با درک عمیق او از بخش هوش مصنوعی صوتی تکمیل می‌شود و او را قادر می‌سازد تا به طور موثر راه حل‌های نوآورانه Respeecher را با نیازهای مشتری و روندهای صنعت هماهنگ کند.


برنامه‌نویسی ناهمزمان در جاوااسکریپت ابزاری قدرتمند برای توسعه برنامه‌های وب کارآمد و پاسخگو است. این امکان را فراهم می‌کند تا کد به صورت غیر مسدودکننده اجرا شود، به طوری که کد دیگر بتواند به اجرای خود ادامه دهد در حالی که یک وظیفه خاص در حال تکمیل است. در این مقاله، مبانی برنامه‌نویسی ناهمزمان در جاوااسکریپت، از جمله مزایا، موارد استفاده رایج و مفاهیم مهم آن را بررسی خواهیم کرد.
پیوند دائمعملی به برنامه‌نویسی ناهمزمان
برنامه‌نویسی ناهمزمان یک الگوی برنامه‌نویسی است که امکان اجرای چندین وظیفه را به صورت موازی فراهم می‌کند. در جاوااسکریپت، این امر از طریق استفاده از توابع بازگشتی (callbacks)، وعده‌ها (promises) و async/await حاصل می‌شود. این تکنیک‌ها به توسعه‌دهندگان اجازه می‌دهند تا کدی پاسخگو و غیر مسدودکننده ایجاد کنند که می‌تواند چندین وظیفه را به طور همزمان مدیریت کند و عملکرد کلی و تجربه کاربری برنامه‌های وب را بهبود بخشد.
پیوند دائممزایای برنامه‌نویسی ناهمزمان
مزایای متعددی در استفاده از برنامه‌نویسی ناهمزمان در جاوااسکریپت وجود دارد، از جمله:

بهبود عملکرد: کد ناهمزمان امکان اجرای چندین وظیفه را به طور همزمان فراهم می‌کند، که می‌تواند به طور قابل توجهی عملکرد کلی برنامه‌های وب را بهبود بخشد.
تجربه کاربری بهتر: کد ناهمزمان می‌تواند به اطمینان از پاسخگو ماندن صفحات وب حتی در هنگام انجام وظایف سنگین مانند بارگیری داده‌ها از یک سرور یا پردازش الگوریتم‌های پیچیده کمک کند.
کد ساده‌تر: تکنیک‌های برنامه‌نویسی ناهمزمان، مانند وعده‌ها و async/await، می‌توانند ساختار کد را ساده کرده و خوانایی و نگهداری آن را آسان‌تر کنند.

پیوند دائعمواردهای استفاده رایج از برنامه‌نویسی ناهمزمان
برنامه‌نویسی ناهمزمان به طور معمول در انواع مختلفی از زمینه‌های توسعه وب استفاده می‌شود، از جمله:

بارگیری داده‌ها: کد ناهمزمان می‌تواند برای بارگیری داده‌ها از یک سرور بدون مسدود کردن رشته اصلی اجرا استفاده شود، که به کاربر اجازه می‌دهد تا در حالی که داده‌ها در حال بارگیری هستند، به تعامل با صفحه وب ادامه دهد.
ورودی کاربر: کد ناهمزمان می‌تواند برای مدیریت ورودی کاربر، مانند کلیک‌های دکمه یا ارسال فرم، به صورت غیر مسدودکننده استفاده شود، که به کاربر اجازه می‌دهد تا در حالی که ورودی در حال پردازش است، به تعامل با صفحه وب ادامه دهد.
انیمیشن: کد ناهمزمان می‌تواند برای ایجاد انیمیشن‌ها و سایر جلوه‌های بصری، مانند اسکرول و جلوه‌های موازی، که به طور روان در پس‌زمینه بدون مسدود کردن رشته اصلی اجرا اجرا می‌شوند، استفاده شود.

پیوند دائمعمفاهیم مهم در برنامه‌نویسی ناهمزمان
برای درک کامل و استفاده موثر از برنامه‌نویسی ناهمزمان در جاوااسکریپت، مهم است که با مفاهیم زیر آشنا باشید:

توابع بازگشتی (Callbacks): یک تابع بازگشتی تابعی است که به عنوان یک آرگومان به تابع دیگری ارسال می‌شود و پس از تکمیل تابع اول اجرا می‌شود. توابع بازگشتی ساده‌ترین و اساسی‌ترین راه برای پیاده‌سازی کد ناهمزمان در جاوااسکریپت هستند.
وعده‌ها (Promises): یک وعده یک شیء است که تکمیل یا شکست نهایی یک عملیات ناهمزمان را نشان می‌دهد. وعده‌ها راهی قدرتمندتر و انعطاف‌پذیرتر برای مدیریت کد ناهمزمان در مقایسه با توابع بازگشتی ارائه می‌دهند.
Async/Await: Async/await یک افزوده جدیدتر به جاوااسکریپت است و راهی برای نوشتن کد ناهمزمان به سبک همزمان ارائه می‌دهد. این به توسعه‌دهندگان اجازه می‌دهد تا کدی بنویسند که شبیه و رفتاری مانند کد همزمان دارد اما به صورت ناهمزمان اجرا می‌شود.

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


برای درک مفهوم برنامه‌نویسی ناهمزمان، به چند نفر فکر کنید که به طور همزمان روی یک پروژه کار می‌کنند، هر کدام وظیفه‌ی متفاوتی را بر عهده دارند.
در برنامه‌نویسی سنتی (همزمان)، هر فرد باید منتظر می‌ماند تا فرد قبل از او کارش را تمام کند تا بتواند کار خود را شروع کند.
اما با برنامه‌نویسی ناهمزمان، همه می‌توانند کار خود را به طور همزمان شروع و انجام دهند، بدون اینکه منتظر اتمام کار دیگران باشند.
به طور مشابه، در یک برنامه‌ی کامپیوتری، برنامه‌نویسی ناهمزمان به برنامه اجازه می‌دهد تا به طور همزمان روی چندین کار کار کند، به جای اینکه یک کار را تمام کند و سپس به سراغ کار بعدی برود. این می‌تواند باعث شود برنامه در مدت زمان کوتاه‌تری کارهای بیشتری را انجام دهد.
به عنوان مثال، یک برنامه می‌تواند در حالی که ورودی کاربر را مدیریت می‌کند و داده‌ها را پردازش می‌کند، یک درخواست را به سرور ارسال کند، همه به طور همزمان. به این ترتیب، برنامه می‌تواند با کارایی بیشتری اجرا شود.

در این مقاله، به دنیای برنامه‌نویسی ناهمزمان در جاوااسکریپت خواهیم پرداخت و تکنیک‌ها و مفاهیم مختلف مورد استفاده برای دستیابی به این الگوی قدرتمند برنامه‌نویسی را بررسی خواهیم کرد.
از توابع بازگشتی تا Promiseها و async/await، شما یاد خواهید گرفت که چگونه قدرت برنامه‌نویسی ناهمزمان را در پروژه‌های جاوااسکریپت خود به کار بگیرید.
درک برنامه‌نویسی ناهمزمان برای ساخت برنامه‌های وب با کارایی بالا ضروری است، چه یک توسعه‌دهنده‌ی باتجربه باشید و چه تازه شروع به یادگیری جاوااسکریپت کرده باشید. پس به خواندن ادامه دهید تا در مورد این مفهوم حیاتی بیشتر بیاموزید.
برنامه‌نویسی همزمان چیست؟
برنامه‌نویسی همزمان روشی است که کامپیوترها کارها را یک به یک و به ترتیبی که دستورالعمل‌ها داده شده‌اند انجام می‌دهند.
تصور کنید در حال پخت شام هستید و فهرستی از کارها دارید، مانند جوشاندن آب برای پاستا، سرخ کردن مرغ و درست کردن سالاد.

شما این کارها را یک به یک انجام می‌دهید و منتظر می‌مانید تا هر کدام تمام شود قبل از رفتن به سراغ کار بعدی.
برنامه‌نویسی همزمان به طور مشابه کار می‌کند، جایی که کامپیوتر یک کار را قبل از رفتن به سراغ کار بعدی به پایان می‌رساند. این باعث می‌شود درک و پیش‌بینی آنچه کامپیوتر در هر زمان معینی انجام می‌دهد آسان شود.
در اینجا یک مثال از کد همزمان در جاوااسکریپت آورده شده است:
// تعریف سه تابع
function firstTask() {
  console.log("Task 1");
}

function secondTask() {
  console.log("Task 2");
}

function thirdTask() {
console.log("Task 3");
}

// اجرای توابع
firstTask();
secondTask();
thirdTask();

این کد پیام‌های زیر را به ترتیب زیر چاپ می‌کند:
"Task 1"
"Task 2"
"Task 3"

کد وظایف را به ترتیبی که می‌بینید اجرا می‌کند و منتظر می‌ماند تا هر وظیفه قبل از رفتن به سراغ وظیفه‌ی بعدی تکمیل شود.

نموداری که نحوه‌ی کار برنامه‌نویسی همزمان را نشان می‌دهد.
با این حال، برنامه‌نویسی همزمان می‌تواند در شرایط خاصی مشکل‌ساز باشد، به ویژه هنگام برخورد با وظایفی که برای تکمیل به زمان قابل توجهی نیاز دارند.
به عنوان مثال، فرض کنید یک برنامه‌ی همزمان وظیفه‌ای را انجام می‌دهد که نیاز به منتظر ماندن برای پاسخ از یک سرور راه دور دارد. برنامه منتظر پاسخ می‌ماند و نمی‌تواند تا زمانی که پاسخ برگردانده شود، کار دیگری انجام دهد. این به عنوان مسدود شدن شناخته می‌شود و می‌تواند منجر به این شود که برنامه برای کاربر غیرپاسخگو یا "منجمد" به نظر برسد.
کد زیر را در نظر بگیرید:
function someLongRunningFunction() {
    let start = Date.now();
    while (Date.now() - start < 5000) {
        // هیچ کاری انجام نده
    }
    return "Hello";
}

console.log('Starting...');

let result = someLongRunningFunction();
console.log(result);

console.log('...Finishing');

در این مثال:

برنامه با چاپ "Starting..." به کنسول شروع می‌شود.
سپس تابع someLongRunningFunction را فراخوانی می‌کند که یک وظیفه‌ی طولانی‌مدت را شبیه‌سازی می‌کند که 5 ثانیه طول می‌کشد تا تکمیل شود. این تابع اجرای بقیه‌ی برنامه را در حالی که در حال اجرا است مسدود می‌کند.
هنگامی که تابع تکمیل می‌شود، "Hello" را برمی‌گرداند و برنامه آن را در کنسول چاپ می‌کند.
در نهایت، برنامه "Finishing" را به کنسول چاپ می‌کند.

در طول 5 ثانیه ای که someLongRunningFunction() در حال اجرا است، برنامه مسدود می‌شود، غیرپاسخگو می‌شود و نمی‌تواند خط کد بعدی را اجرا کند. این می‌تواند باعث شود برنامه زمان زیادی برای تکمیل شدن طول بکشد و برنامه برای کاربر غیرپاسخگو شود.
با این حال، اگر برنامه به طور ناهمزمان اجرا شود، به اجرای خط کد بعدی ادامه می‌دهد تا اینکه مسدود شود. این به برنامه اجازه می‌دهد تا در حالی که منتظر اتمام زمان‌بندی است، پاسخگو بماند و دستورالعمل‌های کد دیگری را اجرا کند.
برنامه‌نویسی ناهمزمان روشی است که یک برنامه‌ی کامپیوتری می‌تواند چندین کار را به طور همزمان انجام دهد، به جای اینکه آن‌ها را یکی پس از دیگری اجرا کند.

نموداری که نحوه‌ی کار برنامه‌نویسی ناهمزمان را نشان می‌دهد.
برنامه‌نویسی ناهمزمان به یک برنامه اجازه می‌دهد تا به کار بر روی سایر وظایف ادامه دهد در حالی که منتظر رویدادهای خارجی مانند درخواست‌های شبکه است. این رویکرد می‌تواند به طور قابل توجهی عملکرد و پاسخگویی یک برنامه را بهبود بخشد.
به عنوان مثال، در حالی که یک برنامه داده‌ها را از یک سرور راه دور دریافت می‌کند، می‌تواند به اجرای سایر وظایف مانند پاسخ دادن به ورودی‌های کاربر ادامه دهد.
در اینجا یک مثال از یک برنامه‌ی ناهمزمان با استفاده از متد setTimeout آورده شده است:
console.log("Start of script");

setTimeout(function() {
  console.log("First timeout completed");
}, 2000);

console.log("End of script");

در این مثال، متد setTimeout یک تابع را پس از یک زمان مشخص اجرا می‌کند. تابعی که به setTimeout منتقل می‌شود به طور ناهمزمان اجرا می‌شود، به این معنی که برنامه به اجرای خط کد بعدی ادامه می‌دهد بدون اینکه منتظر اتمام زمان‌بندی بماند.
هنگامی که کد را اجرا می‌کنید، خروجی به این صورت خواهد بود:
Start of script
End of script
First timeout completed

همانطور که می‌بینید، console.log("First timeout completed") پس از 2 ثانیه اجرا می‌شود. در این بین، اسکریپت به اجرای دستورالعمل کد بعدی ادامه می‌دهد و هیچ رفتار "مسدود" یا "منجمد" ایجاد نمی‌کند.
در جاوااسکریپت، برنامه‌نویسی ناهمزمان را می‌توان از طریق تکنیک‌های مختلفی به دست آورد. یکی از رایج‌ترین روش‌ها استفاده از توابع بازگشتی است.
چگونه از یک تابع بازگشتی استفاده کنیم
فرض کنید می‌خواهید یک جشن تولد برای فرزندتان برنامه‌ریزی کنید. باید مهمانان را دعوت کنید، یک کیک سفارش دهید و بازی‌ها را برنامه‌ریزی کنید. اما همچنین می‌خواهید یک دلقک استخدام کنید تا مهمانان را سرگرم کند. شما فقط می‌توانید پس از اتمام تمام تنظیمات مهمانی و ورود مهمانان، دلقک را به مهمانی بیاورید.

تصویری از یک دلقک
بنابراین، شما به دلقک می‌گویید که فقط پس از اطلاع دادن به او از ورود مهمانان به مهمانی بیاید. در این مورد، دلقک نشان‌دهنده‌ی یک تابع بازگشتی است و "ورود مهمانان" نشان‌دهنده‌ی تابعی است که باید قبل از اجرای تابع بازگشتی، اجرای آن تکمیل شود.
در کد، یک تابع بازگشتی تابعی است که به عنوان یک آرگومان به تابع دیگری منتقل می‌شود و پس از اتمام اجرای تابع اول اجرا می‌شود. معمولاً در جاوااسکریپت برای مدیریت عملیات ناهمزمان مانند واکشی داده‌ها از یک سرور، منتظر ماندن برای ورودی کاربر یا رسیدگی به رویدادها استفاده می‌شود.
در اینجا یک مثال ساده از نحوه استفاده از یک تابع بازگشتی برای مدیریت یک عملیات ناهمزمان آورده شده است:
// تعریف تابع
function fetchData(callback) {
  setTimeout(() => {
    const data = {name: "John", age: 30};
    callback(data);
  }, 3000);
}

// اجرای تابع با یک تابع بازگشتی
fetchData(function(data) {
  console.log(data);
});

console.log("Data is being fetched...");

در این مثال:

ما یک تابع به نام fetchData داریم که از متد setTimeout برای شبیه‌سازی یک عملیات ناهمزمان استفاده می‌کند. این تابع یک تابع بازگشتی را به عنوان یک آرگومان می‌گیرد.
تابع بازگشتی سپس داده‌های واکشی شده توسط تابع پس از اتمام زمان‌بندی را دریافت می‌کند.

متد setTimeout برای اجرای تابع بازگشتی پس از یک زمان مشخص (در این مورد، 3 ثانیه) استفاده می‌شود. تابع بازگشتی به طور ناهمزمان اجرا می‌شود، به این معنی که برنامه به اجرای خط کد بعدی ادامه می‌دهد بدون اینکه منتظر اتمام زمان‌بندی بماند.
هنگامی که کد را اجرا می‌کنید، خروجی به این صورت خواهد بود:
Data is being fetched...
{name: "John", age: 30}

همانطور که می‌بینید، console.log("First timeout completed") پس از 3 ثانیه اجرا می‌شود. در این بین، اسکریپت به اجرای دستورالعمل بعدی ادامه می‌دهد، console.log("Data is being fetched...");.
این مفهوم اصلی برنامه‌نویسی ناهمزمان است. اسکریپت منتظر اتمام عملیات ناهمزمان نمی‌ماند. فقط به اجرای دستورالعمل بعدی ادامه می‌دهد.
جهنم بازگشتی چیست؟
توابع بازگشتی راهی مفید برای مدیریت عملیات ناهمزمان ارائه می‌دهند. با این حال، هنگامی که توابع بازگشتی زیادی در هم تو در تو قرار می‌گیرند، کد می‌تواند پیچیده و درک و خواندن آن دشوار باشد.
این اتفاق زمانی می‌افتد که توابع بازگشتی متعددی را پشت سر هم زنجیر می‌کنید، یک ساختار هرمی از تورفتگی ایجاد می‌کنید که به آن جهنم بازگشتی یا "هرم نابودی" نیز می‌گویند.

در اینجا یک مثال از جهنم بازگشتی آورده شده است:
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getEvenEvenMoreData(c, function(d) {
        getFinalData(d, function(finalData) {
          console.log(finalData);
        });
      });
    });
  });
});

در این مثال:

تابع getData یک تابع بازگشتی را به عنوان یک آرگومان می‌گیرد و پس از واکشی داده‌ها اجرا می‌شود.
تابع بازگشتی سپس داده‌ها را می‌گیرد و تابع getMoreData را فراخوانی می‌کند که همچنین یک تابع بازگشتی را به عنوان یک آرگومان می‌گیرد و غیره.

این تو در تو شدن توابع بازگشتی می‌تواند نگهداری کد را دشوار کند و تورفتگی‌ها درک ساختار کلی کد را حتی سخت‌تر می‌کند.
برای جلوگیری از جهنم بازگشتی، می‌توانید از روش مدرن‌تری برای مدیریت عملیات ناهمزمان به نام Promiseها استفاده کنید. Promiseها راهی ظریف‌تر برای مدیریت جریان ناهمزمان یک برنامه در مقایسه با توابع بازگشتی ارائه می‌دهند. این موضوع در بخش بعدی مورد بحث قرار خواهد گرفت.
Promiseها چگونه کار می‌کنند؟
یک Promise راهی برای مدیریت عملیات ناهمزمان به روشی سازمان‌یافته‌تر است. هدف آن مشابه تابع بازگشتی است، اما قابلیت‌های اضافی و یک نحو قابل خواندن‌تر را ارائه می‌دهد.
یک Promise در جاوااسکریپت یک نگهدارنده برای یک مقدار یا عمل آینده است. با ایجاد یک Promise، اساساً به موتور جاوااسکریپت می‌گویید که "قول" می‌دهد یک عمل خاص را انجام دهد و پس از تکمیل یا شکست، به شما اطلاع دهد.

تصویری از یک Promise و موتور JS
سپس، توابع بازگشتی به Promise متصل می‌شوند تا نتیجه‌ی عمل را مدیریت کنند. این توابع بازگشتی زمانی فراخوانی می‌شوند که Promise برآورده شود (عمل با موفقیت تکمیل شود) یا رد شود (عمل با شکست مواجه شود).
به عنوان یک توسعه‌دهنده‌ی جاوااسکریپت، احتمالاً زمان بیشتری را صرف مصرف Promiseهایی خواهید کرد که توسط APIهای وب ناهمزمان برگردانده می‌شوند و مدیریت نتایج آن‌ها خواهید کرد تا اینکه خودتان آن‌ها را ایجاد کنید.
چگونه یک Promise ایجاد کنیم
برای ایجاد یک Promise، یک نمونه‌ی جدید از شیء Promise را با فراخوانی سازنده‌ی Promise ایجاد می‌کنید.
سازنده یک آرگومان واحد می‌گیرد: یک تابع به نام executor. تابع "executor" بلافاصله هنگام ایجاد Promise فراخوانی می‌شود و دو آرگومان می‌گیرد: یک تابع resolve و یک تابع reject.

نموداری از آناتومی یک Promise.
خط کد زیر را برای اعلام یک Promise بنویسید:
// مقداردهی اولیه یک Promise
const myPromise = new Promise(function(resolve, reject) => {})

اکنون، بیایید شیء myPromise را با ثبت آن در کنسول بررسی کنیم.
console.log(myPromise);

این تصویر نشان‌دهنده‌ی خروجی بررسی شیء Promise است. همانطور که می‌بینید، Promise دارای وضعیت "در انتظار" و مقدار نامشخص است. این به این دلیل است که هنوز هیچ چیزی برای شیء Promise تنظیم نشده است، بنابراین برای همیشه در حالت در انتظار باقی می‌ماند بدون هیچ مقدار یا نتیجه‌ای.
اکنون، بیایید myPromise را طوری تنظیم کنیم که رشته‌ای را پس از 2 ثانیه در کنسول چاپ کند.
const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("Hello from the promise!");
    }, 2000);
});

اکنون، هنگام بررسی شیء myPromise، متوجه خواهید شد که دارای وضعیت "برآورده شده" است و مقداری که به تابع resolve منتقل کرده‌اید را تنظیم کرده است.

یک Promise سه حالت دارد:

در انتظار: حالت اولیه، نه برآورده شده و نه رد شده.
برآورده شده: به این معنی که یک عملیات با موفقیت تکمیل شده است.
رد شده: به این معنی که یک عملیات با شکست مواجه شده است.

حالت‌های در انتظار، برآورده شده و رد شده‌ی یک Promise.
توجه به این نکته مهم است که یک Promise زمانی "تسویه" می‌شود که برآورده یا رد شود.
اکنون که می‌دانید Promiseها چگونه ایجاد می‌شوند، بیایید ببینیم چگونه می‌توانید آن‌ها را مصرف کنید.
چگونه یک Promise را مصرف کنیم
مصرف یک Promise شامل مراحل زیر است:

به دست آوردن یک ارجاع به Promise: برای مصرف یک Promise، ابتدا باید یک ارجاع به آن به دست آورید. بر اساس کد از بخش قبلی، ارجاع ما به یک Promise، شیء myPromise خواهد بود.
اتصال توابع بازگشتی به Promise: هنگامی که یک ارجاع دارید، می‌توانید با استفاده از متدهای .then و .catch، توابع بازگشتی را به آن متصل کنید. متد .then زمانی فراخوانی می‌شود که یک Promise برآورده شود و متد .catch زمانی فراخوانی می‌شود که یک Promise رد شود.
منتظر ماندن برای برآورده شدن یا رد شدن Promise: هنگامی که توابع بازگشتی را به Promise متصل کردید، می‌توانید منتظر بمانید تا Promise برآورده شود یا رد شود.

در اینجا یک مثال از نحوه مصرف یک Promise آورده شده است:
myPromise
    .then((result) => {
        console.log(result);
    })
    .catch((error) => {
        console.log(error);
    });

هنگامی که Promise برآورده شود، متد .then با مقدار حل شده فراخوانی می‌شود. و اگر Promise رد شود، متد .catch با یک پیام خطا فراخوانی می‌شود.
همچنین می‌توانید متد .finally() را اضافه کنید که پس از تسویه Promise فراخوانی می‌شود. این بدان معناست که .finally() صرف نظر از وضعیت Promise (برآورده شده یا رد شده) فراخوانی می‌شود.
myPromise
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    // کد اینجا صرف نظر از وضعیت
    // Promise (برآورده شده یا رد شده) اجرا می‌شود
  });

زنجیره‌سازی Promiseها
زنجیره‌سازی Promise یک الگوی است که به یک رویکرد واضح و آسان برای درک برای مدیریت عملیات ناهمزمان اجازه می‌دهد.
این الگو شامل اتصال چندین Promise در یک دنباله است، جایی که خروجی یک Promise به عنوان ورودی به Promise بعدی منتقل می‌شود.
اتصال Promiseها با استفاده از متد then() انجام می‌شود. این متد یک تابع بازگشتی را به عنوان یک آرگومان می‌گیرد و یک Promise جدید را برمی‌گرداند. Promise جدید با مقداری که توسط تابع بازگشتی برگردانده می‌شود، حل می‌شود.
در اینجا یک مثال از زنجیره‌سازی Promise آورده شده است:
fetch('https://example.com/data')
    .then(response => response.json())
    .then(data => processData(data))
    .then(processedData => {
        // کاری با داده‌های پردازش شده انجام دهید
    })
    .catch(error => console.log(error))

از کد بالا:

Promise اول، که تابع fetch API است، داده‌ها را از یک سرور واکشی می‌کند.
Promise دوم پاسخ را به عنوان JSON تجزیه می‌کند.
Promise سوم داده‌ها را پردازش می‌کند.
Promise چهارم عملیاتی را روی داده‌ها انجام می‌دهد.
متد .catch در انتهای زنجیره هر گونه خطایی را که در هر یک از Promiseهای قبلی رخ داده است، مدیریت می‌کند.

توجه به این نکته مهم است که متدهای .then به طور ناهمزمان و به ترتیب اجرا می‌شوند، هر کدام منتظر حل شدن قبلی هستند و مقدار برگشتی هر .then به عنوان آرگومان به بعدی منتقل می‌شود.
مدیریت خطا
هنگامی که یک Promise رد می‌شود، متد .catch() را فعال می‌کند که خطاها را مدیریت می‌کند. متد .catch() یک آرگومان واحد می‌گیرد که خطای پرتاب شده است.
روش دیگر برای مدیریت خطاها در یک Promise استفاده از بلوک "try-catch" در داخل یک متد .then است.
در اینجا یک مثال آورده شده است:
fetch("https://api.github.com/users/octocat")
  .then((response) => response.json())
  .then((data) => {
    try {
      // پردازش داده‌های دریافتی
      console.log(data);
    } catch (error) {
      console.log(error);
    }
  })
  .catch((error) => console.log(error));

از کد بالا:

تابع fetch() درخواستی را به GitHub API برای واکشی داده‌های کاربر ارسال می‌کند.
بلوک "try-catch" در داخل متد .then دوم برای مدیریت هر گونه خطایی که ممکن است هنگام پردازش داده‌های دریافتی از سرور رخ دهد، استفاده می‌شود.
و متد .catch خارجی فقط خطاهایی را که در طول درخواست fetch رخ داده است، دریافت می‌کند.

توجه به این نکته مهم است که مدیریت خطاها بسیار مهم است زیرا Promiseها برای مدیریت عملیات ناهمزمان استفاده می‌شوند و این عملیات ممکن است به دلایل مختلف با شکست مواجه شوند.
اگر در طول اجرای یک Promise خطایی رخ دهد و مدیریت نشود، برنامه به اجرا ادامه می‌دهد و ممکن است منجر به رفتار غیرمنتظره یا خرابی شود.
با مدیریت خطاها، می‌توانیم اطمینان حاصل کنیم که برنامه حتی در صورت بروز خطا می‌تواند به کار خود ادامه دهد و همچنین بازخورد معناداری را در مورد مشکل به کاربر ارائه دهد.
چگونه از متد Promise.all استفاده کنیم
متد Promise.all() یک آرایه از Promiseها را به عنوان ورودی می‌گیرد و یک Promise واحد را برمی‌گرداند که زمانی که همه Promiseهای ورودی برآورده شوند، برآورده می‌شود. می‌تواند در زمانی که منتظر هستید تا همه Promiseها قبل از انجام کاری حل شوند، مفید باشد.
به عنوان مثال، اگر می‌خواهید داده‌ها را از چندین URL واکشی کنید.
let promise1 = fetch('https://jsonplaceholder.typicode.com/posts/1');
let promise2 = fetch('https://jsonplaceholder.typicode.com/posts/2');
let promise3 = fetch('https://jsonplaceholder.typicode.com/posts/3');

در اینجا، promise1، promise2 و promise3 Promiseهایی هستند که داده‌ها را از سه URL مختلف واکشی می‌کنند.
اکنون، می‌توانید از Promise.all([promise1, promise2, promise3]) برای منتظر ماندن برای حل شدن همه Promiseها قبل از انجام کاری استفاده کنید، همانطور که در زیر نشان داده شده است.
Promise.all([promise1, promise2, promise3])
.then((values) => {
  console.log(values);
})

در مثال بالا:

Promise.all() یک آرایه از Promiseها را به عنوان ورودی می‌گیرد و یک Promise جدید را برمی‌گرداند.
متد then در Promise برگشتی فراخوانی می‌شود تا مقادیر حل شده‌ی تمام Promiseهای ورودی را به ترتیبی که به Promise.all منتقل شده‌اند، ثبت کند.

توجه داشته باشید که در صورتی که هر یک از Promiseهای ورودی رد شوند، Promise برگشتی نیز با مقدار اولین Promise رد شده رد می‌شود.
چگونه از Fetch API با Promiseها استفاده کنیم
من در برخی از مثال‌ها از Fetch API استفاده کرده‌ام و درک می‌کنم که ممکن است برای برخی از خوانندگان ناآشنا باشد. بنابراین این بخش را ایجاد کردم تا اصول Fetch API را برای کسانی که ممکن است نیاز به آشنایی بیشتر با آن داشته باشند، توضیح دهم.
Fetch API یک ویژگی داخلی جاوااسکریپت است که به شما امکان می‌دهد درخواست‌های شبکه را انجام دهید، مانند واکشی داده‌ها از یک سرور. جایگزینی مدرن برای API قدیمی‌تر XMLHttpRequest است و برای آسان‌تر و قدرتمندتر بودن طراحی شده است.
در اینجا یک مثال از نحوه استفاده از Fetch API برای واکشی داده‌ها از یک سرور آورده شده است:
fetch('https://some-api.com/data')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

در این مثال،

متد fetch() برای ارسال درخواستی به سرور واقع در "https://some-api.com/data" استفاده می‌شود. مقدار برگشتی یک Promise است که با پاسخ سرور برآورده می‌شود.
متد .then اول برای مصرف Promise و استخراج داده‌های JSON از پاسخ استفاده می‌شود.
متد then بعدی برای ثبت داده‌های استخراج شده در کنسول استفاده می‌شود.
اگر خطایی رخ دهد، در متد .catch ثبت می‌شود.

امیدوارم توضیح بالا به رفع هر گونه سردرگمی در مورد Fetch API کمک کند و به شما امکان دهد مثال‌های ارائه شده در این مقاله را بهتر درک کنید.
توابع Async با async/await
Async/Await ویژگی است که به شما امکان می‌دهد کد ناهمزمان را به روشی همزمان‌تر و قابل خواندن‌تر بنویسید.

async یک کلمه کلیدی است که برای اعلام یک تابع به عنوان ناهمزمان استفاده می‌شود.
await یک کلمه کلیدی است که در داخل یک تابع async برای مکث اجرای تابع تا زمانی که یک Promise حل شود استفاده می‌شود.

در اینجا یک مثال از نحوه استفاده از async/await آورده شده است:
async function getData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
  const data = await response.json();
  console.log(data);
}

getData();

در این مثال،

تابع getData با استفاده از کلمه کلیدی async به عنوان یک تابع ناهمزمان اعلام می‌شود.
در داخل تابع ناهمزمان، از کلمه کلیدی await برای منتظر ماندن برای تکمیل تابع fetch و واکشی برخی داده‌ها از یک API استفاده می‌کنیم.
هنگامی که داده‌ها واکشی شدند، دوباره از await برای منتظر ماندن و تجزیه داده‌های واکشی شده به عنوان JSON استفاده می‌کنیم.
و در نهایت، داده‌ها را در کنسول ثبت می‌کنیم.

"Async/Await" ابزاری قدرتمند برای مدیریت عملیات ناهمزمان است. این امکان را می‌دهد تا کد خواناتر و قابل نگهداری‌تری با حذف نیاز به توابع بازگشتی و ارائه روشی شهودی‌تر برای مدیریت عملیات ناهمزمان ایجاد شود.
استفاده از کلمه کلیدی "async" قبل از تعریف تابع و کلمه کلیدی "await" قبل از یک عملیات ناهمزمان باعث می‌شود کد شبیه کد همزمان به نظر برسد و درک آن را آسان‌تر می‌کند.
به طور کلی، "Async/Await" یک ابزار ارزشمند در جعبه ابزار توسعه‌دهنده جاوااسکریپت است و می‌تواند به طور قابل توجهی ساده‌سازی مدیریت عملیات ناهمزمان در کد شما را آسان‌تر کند.
نتیجه‌گیری
به طور خلاصه، برنامه‌نویسی ناهمزمان یک مفهوم ضروری در جاوااسکریپت است که به کد شما اجازه می‌دهد بدون مسدود کردن اجرای کد دیگر، در پس‌زمینه اجرا شود.
توسعه‌دهندگان می‌توانند برنامه‌های کارآمدتر و پاسخگوتر ایجاد کنند با استفاده از ویژگی‌هایی مانند توابع بازگشتی، async/await و Promiseها.
درک برنامه‌نویسی ناهمزمان ممکن است در ابتدا دشوار باشد. اما با تمرین و درک عمیق مفاهیم، به ابزاری قدرتمند برای ساخت برنامه‌های وب با کارایی بالا تبدیل می‌شود.
از خواندن این مقاله متشکرم!
اگر از این مقاله لذت بردید و می‌خواهید در مورد برنامه‌نویسی بیشتر بیاموزید، مرا در اینستاگرام در @alege_dev دنبال کنید، جایی که به‌روزرسانی‌ها و نکات منظم در مورد موضوعات برنامه‌نویسی مختلف را ارسال می‌کنم.

به صورت رایگان کدنویسی یاد بگیرید. برنامه درسی منبع باز freeCodeCamp به بیش از 40000 نفر کمک کرده است تا شغل‌هایی به عنوان توسعه‌دهنده به دست آورند. از همین حالا شروع کنید


تصور کنید یک سرآشپز در آشپزخانه پرمشغله یک رستوران کار می‌کند. در یک آشپزخانه همزمان (synchronous)، سرآشپز یک غذا را در یک زمان آماده می‌کرد و منتظر می‌ماند تا هر غذا پخته شود قبل از شروع غذای بعدی. این می‌تواند کند و ناکارآمد باشد، به خصوص زمانی که سفارش‌های زیادی در صف هستند. در یک آشپزخانه ناهمزمان (asynchronous)، با این حال، سرآشپز می‌تواند به طور همزمان روی چندین غذا کار کند، به طور دوره‌ای وضعیت هر کدام را بررسی کند و در صورت لزوم زمان‌های پخت را تنظیم کند. این به سرآشپز اجازه می‌دهد تا سفارش‌ها را به سرعت تکمیل کند و اطمینان حاصل کند که مشتریان غذاهای خود را در اسرع وقت دریافت می‌کنند. به طور مشابه، برنامه‌نویسی ناهمزمان به برنامه‌های کامپیوتری اجازه می‌دهد تا چندین کار را به طور همزمان انجام دهند و در نتیجه اجرای سریع‌تر و کارآمدتری داشته باشند. جاوااسکریپت تک‌رشته‌ای است، به این معنی که فقط یک خط کد می‌تواند در یک زمان اجرا شود. این می‌تواند برای عملکرد مفید باشد، زیرا امکان استفاده کارآمدتر از منابع را فراهم می‌کند، اما همچنین می‌تواند یک محدودیت هنگام برخورد با کارهای پیچیده‌ای باشد که به چندین رشته اجرایی نیاز دارند. در این آموزش، ما اجزای ناهمزمان جاوااسکریپت را بررسی خواهیم کرد. ناهمزمانی به این معنی است که اگر جاوااسکریپت مجبور به منتظر ماندن برای تکمیل یک عملیات باشد، بقیه کد را در حین انتظار اجرا می‌کند. توجه داشته باشید که جاوااسکریپت تک‌رشته‌ای است. این بدان معناست که عملیات‌های ناهمزمان را از طریق صف بازخوانی (callback queue) و حلقه رویداد (event loop) انجام می‌دهد. در جاوااسکریپت همزمان، هر تابع به نوبت اجرا می‌شود و منتظر می‌ماند تا تابع قبلی قبل از اجرای تابع بعدی تکمیل شود. کد همزمان از بالا به پایین نوشته می‌شود. ماهیت جاوااسکریپت و دلیل نیاز به برنامه‌نویسی ناهمزمان جاوااسکریپت تک‌رشته‌ای است و همانطور که قبلاً یاد گرفتیم، یک زمینه اجرایی سراسری دارد. در نتیجه، جاوااسکریپت به طور پیش‌فرض همزمان است و یک پشته فراخوانی واحد دارد. کد به ترتیبی که فراخوانی می‌شود، به ترتیب آخر-در-اول-خارج (LIFO) اجرا خواهد شد. برای درک اینکه جاوااسکریپت همزمان به چه معناست، بیایید قطعه کد زیر را در نظر بگیریم: //جاوااسکریپت همزمان console.log("همزمان."); console.log("جاوااسکریپت همزمان!"); console.log("دوباره همزمان!"); اگر پیش برویم و کد بالا را اجرا کنیم، خواهیم داشت: //خروجی "همزمان" "جاوااسکریپت همزمان!" "دوباره همزمان!" این نشان می‌دهد که جاوااسکریپت همزمان از بالا به پایین اجرا می‌شود، همانطور که می‌توانیم ببینیم کنسول تابع را به صورت خطی از بالا به پایین اجرا می‌کند. با کد بالا، کنسول ابتدا "همزمان" را در ترمینال ثبت می‌کند، سپس "جاوااسکریپت همزمان!" و "دوباره همزمان!". این یک مثال از جاوااسکریپت همزمان است زیرا برنامه کد را به ترتیبی که نوشته شده است اجرا می‌کند. برنامه‌نویسی ناهمزمان حیاتی است زیرا به چندین فرآیند اجازه می‌دهد تا به طور همزمان بدون تداخل با رشته اصلی اجرا شوند. این مهم است زیرا رشته اصلی مسئول مدیریت پشته فراخوانی است که یک ساختار داده است که توالی فعلی فراخوانی توابع را نگه می‌دارد. مسدود شدن رشته اصلی منجر به کاهش عملکرد می‌شود زیرا برنامه‌نویسی ناهمزمان به رشته اصلی اجازه می‌دهد تا بدون مسدود شدن بماند و وظایف اضافی می‌توانند در حالی که وظیفه ناهمزمان در حال انجام است تکمیل شوند. بیایید به یک فراخوانی تابع جاوااسکریپت به نام تابع setTimeout() نگاه کنیم که به ما امکان می‌دهد کد جاوااسکریپت را پس از مدت زمان مشخصی اجرا کنیم. پس از زمان مشخص شده، متد setTimeout() یک بلوک کد را اجرا می‌کند. این متد فقط کد را یک بار اجرا می‌کند. برای کار با تابع setTimeout()، باید از یک تابع پیکانی (arrow function) که در ES6 وجود دارد استفاده کنیم، همانطور که در زیر نشان داده شده است: //جاوااسکریپت ناهمزمان setTimeout(function, milliseconds); اینها پارامترها هستند: function: تابعی که شامل یک بلوک کد است. milliseconds: زمانی که طول می‌کشد تا تابع اجرا شود برای توضیح این موضوع، بیایید به کد زیر نگاه کنیم: console.log("ناهمزمان."); setTimeout(() => console.log("جاوااسکریپت ناهمزمان!"), 3000); console.log("دوباره ناهمزمان!"); کد بالا به طور همزمان در موتور جاوااسکریپت اجرا نمی‌شود، بر خلاف مثال قبلی ما. بیایید به خروجی نمایش داده شده در زیر نگاه کنیم: // ناهمزمان. // دوباره ناهمزمان! // جاوااسکریپت ناهمزمان! مرحله 1: خط اول کد اجرا می‌شود و رشته "ناهمزمان" را در کنسول ثبت می‌کند. مرحله 2: متد setTimeout فراخوانی می‌شود که تابع ناشناس را پس از 3 ثانیه (3000 میلی‌ثانیه) اجرا می‌کند. تابع ناشناس رشته "جاوااسکریپت ناهمزمان!" را در کنسول ثبت می‌کند. مرحله 3: خط سوم کد اجرا می‌شود و رشته "دوباره ناهمزمان!" را در کنسول ثبت می‌کند. مرحله 4: پس از 3 ثانیه، تابع ناشناس از متد setTimeout اجرا می‌شود و رشته "جاوااسکریپت ناهمزمان!" را در کنسول ثبت می‌کند. به عبارت دیگر، با جاوااسکریپت ناهمزمان، جاوااسکریپت منتظر پاسخ نمی‌ماند قبل از ادامه اجرای توابع بعدی. این برای برنامه‌هایی که به قدرت پردازشی زیادی نیاز دارند مفید است زیرا به انجام چندین کار به طور همزمان اجازه می‌دهد. تکنیک‌هایی برای نوشتن جاوااسکریپت ناهمزمان جاوااسکریپت می‌تواند به طور ناهمزمان رفتار کند. بیایید نگاهی به برخی از تکنیک‌هایی که برای جاوااسکریپت ناهمزمان مفید هستند بیاندازیم: ما در مورد موارد زیر بحث خواهیم کرد: promises async/await callbacks بازخوانی‌ها (Callbacks): این به نوشتن کد ناهمزمان به روشی همزمان اجازه می‌دهد. وعده‌ها (Promises): نوشتن کد جاوااسکریپت ناهمزمان با وعده‌ها آسان می‌شود. آنها به شما امکان می‌دهند کدی ایجاد کنید که پس از یک دوره زمانی مشخص یا زمانی که یک شرط مشخص برآورده شد اجرا شود. Async/await: async/await یک تکنیک جدیدتر برای ایجاد کد جاوااسکریپت ناهمزمان است. این به شما کمک می‌کند کدی مختصرتر و خواناتر بنویسید که پس از یک دوره زمانی مشخص یا زمانی که یک شرط مشخص برآورده شد اجرا شود. بازخوانی‌ها (Callbacks) در جاوااسکریپت، توابع شهروندان درجه یک هستند. بنابراین، می‌توانید یک تابع را به عنوان یک آرگومان به تابع دیگر منتقل کنید. به طور خلاصه، یک بازخوانی تابعی است که پس از اتمام اجرای کد بیرونی اجرا می‌شود. به عنوان ورودی به تابع دیگری ارائه می‌شود. هنگامی که یک تابع هدف خود را تکمیل می‌کند، برای فراخوانی تابع دیگری استفاده می‌شود. بیایید به مثال کد زیر نگاه کنیم: function incrementDigits(callback) { callback(); } تابع incrementDigits( ) یک تابع دیگر را به عنوان پارامتر می‌گیرد و آن را در داخل خود فراخوانی می‌کند. این در جاوااسکریپت معتبر است و ما آن را یک بازخوانی می‌نامیم. بنابراین، تابعی که به عنوان پارامتر به تابع دیگری منتقل می‌شود، یک تابع بازخوانی است. بازخوانی‌ها به طور گسترده برای انجام اقدامات ناهمزمان در جاوااسکریپت، مانند اجرای یک درخواست AJAX یا منتظر ماندن برای کلیک کاربر استفاده می‌شوند. با یادگیری در مورد تابع setTimeout() در بخش قبلی، می‌توانید توابع بازخوانی را به عنوان یک تابع پیکانی ES6 نیز بنویسید که نوع جدیدی از تابع در جاوااسکریپت است: setTimeout(() => { console.log("خروجی پس از 5 ثانیه آغاز می‌شود"); }, 5000); در این کد، متد setTimeout() برای به تعویق انداختن اجرای یک تابع استفاده می‌شود. با استفاده از تابع، که در این مورد یک تابع پیکانی است، رشته "خروجی پس از 5 ثانیه آغاز می‌شود" را در کنسول ثبت می‌کند. تأخیر مشخص شده 5000 میلی‌ثانیه (یا 5 ثانیه) است. پس از تکمیل عملیات ناهمزمان، تابع بازخوانی با نتیجه عملیات فراخوانی می‌شود. function incrementDigits(num, callback) { setTimeout(function() { num++; console.log(num); if (num < 10) { incrementDigits(num, callback); } else { callback(); } }, 1000); } incrementDigits(0, function() { console.log('تمام شد!'); }); این کد یک مثال از جهنم بازخوانی است. این یک تابع بازگشتی است که تا زمانی که عدد بزرگتر از 10 شود، خود را فراخوانی می‌کند. سپس، تابع بازخوانی را اجرا می‌کند. تابع setTimeout() برای به تأخیر انداختن اجرای کد استفاده می‌شود تا یک فرآیند طولانی‌تر را شبیه‌سازی کند. این تابع هر عدد را از 0 شروع می‌کند تا زمانی که به 10 برسد و سپس پیام "تمام شد!" چاپ می‌شود. در نتیجه، کد می‌تواند به طور ناهمزمان اجرا شود، به این معنی که توابع می‌توانند به طور همزمان اجرا شوند و منتظر تکمیل یکدیگر نباشند. ما قبلاً دیده‌ایم که چگونه یک بازخوانی ناهمزمان را ایجاد کنیم. نحوه عملکرد توابع ناهمزمان و بازخوانی‌ها در زیر کاپوت وقتی یک تابع ناهمزمان با اصطلاح awaits مواجه می‌شود، اجرای آن را متوقف می‌کند تا زمانی که وعده برآورده شود، در حالی که یک بازخوانی ناهمزمان یک تابع سطح بالاتر است که تابع بازخوانی ناهمزمان را به روشی غیرمسدودکننده اجرا می‌کند. در حالی که یک وعده ثابت است، تابع ناهمزمان اجرای خود را متوقف می‌کند. ما می‌توانیم توابع ناهمزمان را طوری بنویسیم که انگار همزمان هستند (به ترتیب اجرا می‌شوند) با استفاده از await، که تا زمانی که وعده برآورده شود (اجرای موفقیت‌آمیز) اجرا را متوقف می‌کند. مدیریت جهنم بازخوانی در جاوااسکریپت، راه ایجاد یک تابع بازخوانی، انتقال آن به عنوان پارامتر به تابع دیگر و سپس فراخوانی آن پس از تکمیل کار است. راه‌هایی برای مدیریت بازخوانی‌ها وجود دارد. استفاده از وعده: می‌توان یک وعده برای هر بازخوانی ایجاد کرد. وقتی بازخوانی موفقیت‌آمیز باشد، وعده برآورده می‌شود و اگر بازخوانی با شکست مواجه شود، وعده رد می‌شود. استفاده از async-await: توابع ناهمزمان با استفاده از await به ترتیب اجرا می‌شوند. اجرای آن تا زمانی که وعده برآورده شود و اجرای تابع موفقیت‌آمیز باشد متوقف می‌شود. وعده یک شیء جاوااسکریپت است که موفقیت یا شکست نهایی یک عملیات ناهمزمان را نشان می‌دهد. این به شما امکان می‌دهد کدی ناهمزمان ایجاد کنید که کار می‌کند و به نظر می‌رسد همزمان است. یک وعده، در زمینه ما، چیزی است که انجام آن مدتی طول می‌کشد. یک وعده سه حالت ممکن دارد: در انتظار، برآورده شده یا رد شده. در حالی که یک وعده هنوز برآورده نشده است، یک عملیات ناهمزمان هنوز در حال انجام است. برآورده شدن یک وعده نشان می‌دهد که یک عملیات ناهمزمان با موفقیت تکمیل شده است. با استفاده از سازنده وعده، می‌توانید یک وعده به این صورت ایجاد کنید: let promise = new Promise(function(resolve, reject) { // ایجاد یک وعده جدید برای برآورده کردن یا رد کردن }); تابع سازنده یک تابع را به عنوان آرگومان می‌گیرد. این تابع به عنوان تابع executor نامیده می‌شود. // سازنده وعده به عنوان آرگومان function(resolve, reject) { // doSomethingHere } تابع executor دو آرگومان، resolve و reject را می‌گیرد. منطق شما در داخل تابع executor می‌رود که به طور خودکار هنگام ایجاد یک وعده جدید اجرا می‌شود و کد را به شرح زیر پردازش می‌کند: مرحله 1: یک وعده جدید با استفاده از سازنده وعده و دو آرگومان، resolve و reject ایجاد می‌شود. مرحله 2: متد `.then()` در وعده فراخوانی می‌شود که دو تابع بازخوانی را می‌گیرد، یکی برای زمانی که وعده برآورده شود و دیگری برای زمانی که وعده رد شود. مرحله 3: تابع بازخوانی اول در صورت برآورده شدن موفقیت‌آمیز وعده اجرا می‌شود، در حالی که تابع بازخوانی دوم در صورت رد شدن وعده اجرا می‌شود. فراخوانی تابع resolve نشان‌دهنده تکمیل موفقیت‌آمیز است، و یک وعده رد شده نشان می‌دهد که عملیات ناهمزمان با شکست مواجه شده است. تبدیل یک بازخوانی به یک وعده برای تبدیل یک بازخوانی به یک وعده، اولین قدم ایجاد یک شیء وعده جدید است. سازنده وعده دو آرگومان می‌گیرد: تابعی که به آن دو پارامتر، resolve و reject منتقل می‌شوند. هنگامی که یک عملیات ناهمزمان با موفقیت انجام می‌شود، تابع resolve فراخوانی می‌شود و هنگامی که عملیات با شکست مواجه می‌شود، تابع reject فراخوانی می‌شود. بازخوانی در داخل این تابع فراخوانی می‌شود و نتیجه به تابع resolve یا reject بسته به نتیجه عملیات منتقل می‌شود. پس از فعال شدن بازخوانی، شیء وعده برگردانده می‌شود تا بتوان از آن در پردازش‌های بعدی استفاده کرد. با تبدیل بازخوانی‌ها به وعده‌ها، توسعه‌دهندگان می‌توانند کدی را بنویسند که مختصرتر و درک آن آسان‌تر است. const incrementDigits = num => { return new Promise((resolve, reject) => { setTimeout(() => { num++; console.log(num); if (num < 10) { resolve(incrementDigits(num)); } else { resolve('تمام شد!'); } }, 1000); }); }; incrementDigits(0).then(res => console.log(res)); در این کد از وعده‌ها برای انجام یک عملیات ناهمزمان استفاده می‌شود. وعده‌ها راهی هوشمندانه برای رفع مشکلاتی هستند که توسط جهنم بازخوانی ایجاد می‌شوند. با کد کمتر و تکنیک‌های ساده‌تر، می‌توانید از این روش برای بازیابی ترتیبی داده‌ها از چندین نقطه پایانی استفاده کنید. کد بالا تابعی به نام incrementDigits() ایجاد می‌کند که یک آرگومان num (فرض بر اینکه یک عدد است) می‌گیرد. سپس یک وعده جدید ایجاد می‌کند و دو آرگومان resolve و reject (که هر دو تابع هستند) را به آن منتقل می‌کند. مراحل بعدی عبارتند از: تنظیم یک تایم‌اوت یک ثانیه‌ای، افزایش مقدار num، ثبت مقدار جدید num و اگر num کمتر از 10 باشد، برآورده کردن وعده و فراخوانی تابع یک بار دیگر با num به عنوان آرگومان. اگر مقدار num بزرگتر از 10 باشد، وعده برآورده می‌شود و رشته "تمام شد!" ثبت می‌شود. در نهایت، وعده فراخوانی می‌شود و نتیجه ثبت می‌شود. برای مدیریت وعده و برگرداندن نتیجه عملیات ناهمزمان incrementDigits()، از متد .then()` استفاده کنید. اگر یک وضعیت خطا در داخل یک وعده رخ دهد، می‌توانید با فراخوانی تابع reject() با یک خطا، وعده را "رد" کنید. برای مدیریت رد شدن یک وعده، یک تابع بازخوانی را به تابع catch() منتقل می‌کنید. متدهای هندلر وعده این هندلرها توابع ساده‌ای هستند که به ایجاد پیوند بین توابع executor و consumer کمک می‌کنند تا زمانی که یک وعده برآورده یا رد شود، هماهنگ شوند. با این حال، به جای اینکه فقط مقدار را برگردانیم، یک شیء حاوی این داده‌ها را برمی‌گردانیم. متدهای اصلی هندلر وعده عبارتند از: .then() .catch() .finally() متدهای هندلر وعده دیگری نیز وجود دارد، مانند .all() که یک وعده واحد ایجاد می‌کند که زمانی که همه وعده‌ها در یک آرایه برآورده شوند، برآورده می‌شود و .race() که یک وعده واحد ایجاد می‌کند که به محض برآورده شدن یا رد شدن یکی از وعده‌ها در یک آرایه، برآورده یا رد می‌شود. Async/await در بخش بالا، قبلاً دیده‌ایم که چگونه یک بازخوانی ناهمزمان ایجاد کنیم. استفاده از await، که تا زمانی که وعده برآورده شود، اجرا را متوقف می‌کند، به ما امکان می‌دهد توابع ناهمزمان را طوری بنویسیم که انگار همزمان هستند (به ترتیب اجرا می‌شوند). async/await یک تکنیک برای ساخت کد ناهمزمان است که شبیه کد همزمان به نظر می‌رسد و رفتار می‌کند. این به شما امکان می‌دهد کدی ایجاد کنید که به نظر می‌رسد به ترتیب اجرا می‌شود اما به طور ناهمزمان کار می‌کند. این تابع خواندن و نوشتن کدی را که از عملیات‌های ناهمزمان استفاده می‌کند، ساده می‌کند. async/await یک فناوری جاوااسکریپت است که به شما امکان می‌دهد کد ناهمزمان را به طور همزمان‌تر ایجاد کنید. مشابه بازخوانی‌ها، زنجیر کردن وعده‌ها می‌تواند بسیار دست و پا گیر و گیج کننده شود. به همین دلیل async و await توسعه یافتند. توابع async به شرح زیر تعریف می‌شوند: //async/await const newAsyncFunc = async () => { }; سپس می‌توانید از کلمه کلیدی await در داخل این تابع async برای دستور دادن به آن برای منتظر ماندن برای چیزی استفاده کنید: const result = asyncFunc(); const asyncFunc = async() => { const result = await asyncFunc(); //doSomethingNow } برای انجام یک عملیات ناهمزمان، تابع incrementDigits() را می‌توان با استفاده از توابع ناهمزمان و اصطلاح async/await نوشت. // مثال با استفاده از async/await const incrementDigits = async num => { num++; console.log(num); if (num < 10) { await incrementDigits(num); } else { return 'تمام شد!'; } }; (async () => { const res = await incrementDigits(0); console.log(res); })(); از نحو async/await در این قطعه کد برای افزایش یک عدد تا زمانی که به 10 برسد استفاده می‌شود. کلمه کلیدی async که قبل از اعلان تابع incrementDigits() می‌آید، نشان می‌دهد که تابع یک وعده برمی‌گرداند. سپس، برای متوقف کردن اجرای تابع incrementDigits() تا زمانی که وعده برآورده شود، از کلمه کلیدی await استفاده می‌شود. هنگامی که وعده برآورده شد، کدی که بعد از کلمه کلیدی await می‌آید اجرا می‌شود. مدیریت خطا در جاوااسکریپت ناهمزمان خطاها می‌توانند خطاهای کدنویسی ایجاد شده توسط برنامه‌نویس، خطاهای ورودی یا سایر موارد غیرقابل پیش‌بینی باشند. مدیریت خطا در جاوااسکریپت ناهمزمان شامل بلوک‌های try/catch است که در جاوااسکریپت برای گرفتن هر گونه اشتباهی که ممکن است در طول فرآیند ناهمزمان رخ دهد استفاده می‌شود. try{ //... }catch(e){ //... } یک دستور try به شما امکان می‌دهد یک بلوک کد را برای خطاها آزمایش کنید. یک دستور catch به شما امکان می‌دهد آن خطا را مدیریت کنید. به عنوان مثال: try{ alert("این یک تابع خطا است!") }catch(e){ alert(e) } شامل یک متد .catch() در انتهای عملیات ناهمزمان به شما امکان می‌دهد هر گونه خطای احتمالی را مدیریت کنید. یک بلوک catch می‌تواند پارامترهایی داشته باشد که اطلاعات خطا را در اختیار شما قرار می‌دهد. به طور کلی، بلوک catch برای ثبت خطا یا نمایش پیام‌های خاص به کاربر استفاده می‌شود. اکنون که تابع ناهمزمان incrementDigits() یک تابع مدیریت خطا دارد، می‌توانیم آن را به شرح زیر شامل کنیم: const incrementDigits = async num => { try{ num++; console.log(num); if (num < 10) { await incrementDigits(num); } else { return 'تمام شد!'; } } catch (err) { console.log(err); } }; (async () => { const res = await incrementDigits(0); console.log(res); })(); نتیجه‌گیری در این پست، ما جاوااسکریپت ناهمزمان را بررسی کردیم، نحوه عملکرد داخلی توابع ناهمزمان جاوااسکریپت را بررسی کردیم و یاد گرفتیم که چگونه با استفاده از وعده‌ها و async/await جاوااسکریپت ناهمزمان ایجاد کنیم. ما دیده‌ایم که بازخوانی‌ها توابع ساده‌ای هستند که به عنوان پارامتر به توابع دیگر منتقل می‌شوند و فقط پس از تکمیل یک رویداد اجرا می‌شوند. برنامه‌نویسی ناهمزمان به ویژه هنگام برخورد با فرآیندهای زمان‌بر مانند درخواست‌های شبکه یا عملیات فایل مفید است. با انجام این فعالیت‌ها به طور ناهمزمان، کامپیوتر می‌تواند قطعات کد دیگری را در حالی که منتظر نتایج است اجرا کند و کارایی منابع را افزایش دهد و سرعت اجرا را افزایش دهد. برنامه‌نویسی ناهمزمان برای مدیریت تعاملات و رویدادها در توسعه وب مدرن نیز ضروری است. این به برنامه‌نویسان امکان می‌دهد تا به سرعت به رویدادهای کاربر پاسخ دهند بدون اینکه رابط کاربری متوقف یا کند شود. با افزایش برنامه‌های تک صفحه‌ای و نیاز به تعاملات روان کاربر، برنامه‌نویسی ناهمزمان اهمیت بیشتری پیدا می‌کند. برای یادگیری بیشتر، در اینجا چند منبع در مورد جاوااسکریپت ناهمزمان وجود دارد.


جاوا اسکریپت توانایی اجرای دستورالعمل‌های ناهمزمان (یا async) را دارد. این دستورالعمل‌ها در پس‌زمینه اجرا می‌شوند تا زمانی که پردازش آن‌ها به پایان برسد.
دستورالعمل‌های ناهمزمان موتور جاوا اسکریپت را از پذیرش و پردازش دستورالعمل‌های بیشتر باز نمی‌دارند. به همین دلیل است که جاوا اسکریپت به طور ذاتی غیر مسدودکننده است.
چندین ویژگی ناهمزمان در جاوا اسکریپت وجود دارد و یکی از آن‌ها Promises است. برای کار با Promises، باید از یک نحو خاص استفاده کنید که نوشتن دستورالعمل‌های async را بسیار سازمان‌یافته‌تر می‌کند. کار با Promises یک مهارت بسیار مفید است که هر توسعه‌دهنده جاوا اسکریپت باید یاد بگیرد.
این مقاله یک راهنمای عمیق برای Promises در جاوا اسکریپت است. شما یاد خواهید گرفت که چرا جاوا اسکریپت Promises دارد، یک Promise چیست و چگونه با آن کار کنید. همچنین یاد خواهید گرفت که چگونه از async/await—ویژگی‌ای که از Promises مشتق شده است—استفاده کنید و صف کار (job queue) چیست.
در اینجا موضوعاتی که پوشش خواهیم داد آمده است:

چرا باید در مورد Promises اهمیت قائل شوید؟
Promise چیست؟
چگونه یک Promise در جاوا اسکریپت ایجاد کنیم
چگونه یک callback به یک Promise متصل کنیم
چگونه خطاها را در یک Promise مدیریت کنیم
چگونه چندین Promise را به طور همزمان مدیریت کنیم
نحو async/await چیست؟
چگونه یک تابع async در جاوا اسکریپت ایجاد کنیم
چگونه از کلمه کلیدی await استفاده کنیم
چگونه خطاها را در async/await مدیریت کنیم
صف کار چیست؟

این راهنما نوید یک مطالعه جالب و روشنگر را می‌دهد. :) این برای هر کسی که به دنبال بهبود نوشتن دستورالعمل‌های async جاوا اسکریپت و در نتیجه استفاده مناسب از آنچه زبان ارائه می‌دهد، است، طراحی شده است. با تمام این مقدمات، بیایید شروع کنیم.
پیش‌نیازها
برای دنبال کردن مطالب و درک آن، چند نکته وجود دارد که باید داشته باشید:

دانش پایه جاوا اسکریپت
دانش نحوه پردازش عملیات‌های async جاوا اسکریپت

دانستن این موضوعات به شما کمک می‌کند تا به درستی آنچه را که قرار است یاد بگیرید، درک کنید. اگر پیش‌نیازها را ندارید، می‌توانید آن‌ها را یاد بگیرید و برگردید. این مقاله از برخی مفاهیم آن موضوعات در اینجا استفاده خواهد کرد.
چرا باید در مورد Promises اهمیت قائل شوید؟
Promises همیشه بخشی از جاوا اسکریپت نبوده‌اند. Callbackها با توابع ناهمزمان همکاری می‌کردند تا نتایج مورد نظر را در گذشته تولید کنند. یک callback هر تابعی است که پارامتر یک تابع async است که تابع async آن را برای تکمیل عملیات خود فراخوانی می‌کند.
برای فراخوانی یک تابع async، باید یک callback را به عنوان یک آرگومان به این صورت ارسال کنید:
function callback(result) {
  // استفاده از نتیجه عملیات Async
}

randomAsyncOperation((response) => callback(response));

اما callbackها یک مشکل بزرگ داشتند. نشان دادن مشکل، درک آن را آسان‌تر می‌کند.
فرض کنید یک تابع ناهمزمان دارید که داده‌ها را از جایی در اینترنت واکشی می‌کند. این تابع باید دو callback، successCallback و failureCallback را بپذیرد.
successCallback در صورت موفقیت‌آمیز بودن عملیات و یافتن منبع مناسب اجرا می‌شود. اما failureCallback در صورت ناموفق بودن عملیات و عدم یافتن منبع اجرا می‌شود.
function SuccessCallback(result) {
  console.log("Resource found", result);
}

function failureCallback(error) {
  console.error("Ooops. Something went wrong", error);
}

برای اجرای تابع async، باید دو تابع callback را به عنوان آرگومان ارسال کنید:
fetchResource(url, successCallback, failureCallback)

در اینجا، url متغیری است که نشان‌دهنده مکان منبع است.
این کد برای اکنون به خوبی اجرا می‌شود. شما از هر دو سناریوی احتمالی که تابع می‌تواند با آن روبرو شود، مراقبت کرده‌اید. شما یک callback برای یک عملیات موفقیت‌آمیز و یک callback برای یک عملیات ناموفق دارید.
حالا فرض کنید می‌خواهید عملیات‌های واکشی زیادی انجام دهید، اما هر عملیات باید موفقیت‌آمیز باشد تا عملیات بعدی اجرا شود. این مفید است اگر داده‌هایی که به آن نیاز دارید باید به ترتیب خاصی بیایند و نتوانند پراکنده شوند.
به عنوان مثال، ممکن است با این وضعیت روبرو شوید اگر نتیجه عملیات بعدی به نتیجه عملیات قبلی بستگی داشته باشد.
در این حالت، callbackهای موفقیت شما callbackهای موفقیت خود را خواهند داشت که مهم است زیرا در صورت دریافت آن‌ها باید از نتایج استفاده کنید.
fetchResource(
  url,
  function (result) {
    // انجام کاری با نتیجه
    fetchResource(
      newUrl,
      function (result) {
        // انجام کاری با نتیجه جدید
        fetchResource(
          anotherUrl,
          function (result) {
            // انجام کاری با نتیجه جدید
          },
          failureCallback
        );
      },
      failureCallback
    );
  },
  failureCallback
);

از مثال، ممکن است متوجه پیچیدگی در حال توسعه شوید. شما باید callbackهای موفقیت خود را به طور مکرر در هم تو در تو کنید و در هر بار فراخوانی تابع async، failureCallback را تکرار کنید.
این callbackهای تو در تو منجر به "هرم نفرین callback" یا جهنم callback شد که می‌تواند به سرعت به یک کابوس تبدیل شود. آیا راه کارآمدتری برای رسیدگی به این نوع موقعیت‌ها وجود دارد؟
جاوا اسکریپت Promises را به عنوان بخشی از ES6 (ES2015) معرفی کرد تا این مشکل را حل کند. این کار باعث ساده‌تر شدن کار با callbackها و ایجاد نحو بهتری شد، همانطور که بعداً خواهید دید. Promises اکنون پایه و اساس بیشتر عملیات‌های ناهمزمان مدرنی هستند که توسعه‌دهندگان در جاوا اسکریپت امروزی از آن‌ها استفاده می‌کنند.
Promise چیست؟

تصویر اعتبار: https://gifer.com
یک Promise یک اطمینان یا تضمین است که چیزی در آینده اتفاق خواهد افتاد. یک فرد می‌تواند نتیجه یا نتیجه خاصی را به فرد دیگری وعده دهد. Promises محدود به افراد نیستند، دولت‌ها و سازمان‌ها نیز می‌توانند وعده دهند. احتمالاً قبلاً یک وعده داده‌اید.
با این اطمینان (Promise) دو نتیجه احتمالی وجود دارد - تحقق یا شکست. یک Promise به نتیجه‌ای گره خورده است که نشان می‌دهد محقق شده است. اگر آن نتیجه رخ ندهد، Promise شکست می‌خورد. یک Promise در نهایت باید یکی از این نتایج را داشته باشد.
در جاوا اسکریپت، یک Promise یک شی است که یک مقدار واحد را در زمان مشخصی در آینده تولید می‌کند. اگر Promise موفقیت‌آمیز باشد، یک مقدار حل شده تولید می‌کند، اما اگر مشکلی پیش بیاید، دلیلی برای شکست Promise تولید می‌کند. نتایج احتمالی در اینجا شبیه به Promises در زندگی واقعی است.
Promises جاوا اسکریپت می‌توانند در یکی از سه حالت ممکن باشند. این حالت‌ها پیشرفت Promise را نشان می‌دهند. آن‌ها عبارتند از:

در انتظار (pending): این حالت پیش‌فرض یک Promise تعریف شده است
محقق شده (fulfilled): این حالت یک Promise موفقیت‌آمیز است
رد شده (rejected): این حالت یک Promise ناموفق است

یک Promise از حالت در انتظار به حالت محقق شده یا از حالت در انتظار به حالت رد شده می‌رود - "محقق شده" و "رد شده" نشان‌دهنده پایان یک Promise است.
از این به بعد، این مقاله به شیء جاوا اسکریپت به عنوان "Promise" اشاره خواهد کرد.
چگونه یک Promise در جاوا اسکریپت ایجاد کنیم
برای ایجاد یک Promise، باید یک شیء نمونه با استفاده از تابع سازنده Promise ایجاد کنید. تابع سازنده Promise یک پارامتر می‌گیرد. آن پارامتر تابعی است که تعریف می‌کند چه زمانی Promise جدید را حل کند و به صورت اختیاری چه زمانی آن را رد کند.
const promise = new Promise((resolve, reject) => {
  // شرطی برای حل یا رد Promise
});

به عنوان مثال، فرض کنید می‌خواهید یک Promise پس از یک وقفه دو ثانیه‌ای حل شود. می‌توانید این کار را با نوشتن آن در پارامتر تابع سازنده انجام دهید.
const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Done!"), 2000);
});

در Promises، resolve یک تابع با یک پارامتر اختیاری است که نشان‌دهنده مقدار حل شده است. همچنین، reject یک تابع با یک پارامتر اختیاری است که نشان‌دهنده دلیل شکست Promise است. در مثال بالا، مقدار حل شده Promise رشته 'Done!' است.
در اینجا یک مثال دیگر وجود دارد که نشان می‌دهد چگونه می‌توانید یک Promise را بر اساس شرایطی که تنظیم می‌کنید، حل یا رد کنید.
const promise = new Promise((resolve, reject) => {
  const num = Math.random();
  if (num >= 0.5) {
    resolve("Promise is fulfilled!");
  } else {
    reject("Promise failed!");
  }
});

از این مثال‌ها می‌توانید ببینید که شما کنترل دارید که چه زمانی Promise خود را حل یا رد کنید و می‌توانید آن را به یک شرط خاص گره بزنید. با این، شما یاد گرفتید که چگونه یک Promise در جاوا اسکریپت ایجاد کنید.
چگونه یک Callback به یک Promise متصل کنیم
برای ایجاد یک callback برای یک Promise، باید از متد .then() استفاده کنید. این متد دو تابع callback را می‌گیرد. تابع اول در صورت حل Promise اجرا می‌شود، در حالی که تابع دوم در صورت رد Promise اجرا می‌شود.
const promise = new Promise((resolve, reject) => {
  const num = Math.random();
  if (num >= 0.5) {
    resolve("Promise is fulfilled!");
  } else {
    reject("Promise failed!");
  }
});

function handleResolve(value) {
  console.log(value);
}

function handleReject(reason) {
  console.error(reason);
}

promise.then(handleResolve, handleReject);
// Promise is fulfilled!
// or
// Promise failed!

این روش برای رسیدگی به نتایج احتمالی Promise شما است. هرگونه خطای بدون رسیدگی در Promise شما آن‌ها را در حالت رد شده در پایان نگه می‌دارد، اما خطاهای رسیدگی شده باعث می‌شود عملیات یک Promise محقق شده را برگرداند.
امکان ایجاد یک Promise که بلافاصله حل شده است و سپس اتصال یک callback با متد .then() وجود دارد. همچنین می‌توانید یک Promise که بلافاصله رد شده است را به همان روش ایجاد کنید.
Promise.resolve("Successful").then((result) => console.log(result));
// Successful

Promise.reject("Not successful").then((result) => console.log(result));
// Error: Uncaught (in promise)

خطا در Promise رد شده به این دلیل است که باید یک callback جداگانه برای رسیدگی به یک Promise رد شده تعریف کنید.
Promise.reject("Not successful").then(
  () => {
    /*Empty Callback if Promise is fulfilled*/
  },
  (reason) => console.error(reason)
);
// Not Successful

اکنون شما به درستی یک نتیجه رد شده را مدیریت کرده‌اید.
Promises کار را به طرز باورنکردنی آسان می‌کنند تا دستورالعمل‌های ناهمزمان را زنجیر کنید. وقتی یک Promise را با متد .then() مدیریت می‌کنید، عملیات همیشه یک Promise دیگر را برمی‌گرداند. با استفاده از این رویکرد، می‌توانید از "هرم نفرین callback" قبلی جلوگیری کنید.
در نظر بگیرید کدی که قبلاً باعث ایجاد ساختار هرمی شده بود:
fetchResource(
  url,
  function (result) {
    // انجام کاری با نتیجه
    fetchResource(
      newUrl,
      function (result) {
        // انجام کاری با نتیجه جدید
        fetchResource(
          anotherUrl,
          function (result) {
            // انجام کاری با نتیجه جدید
          },
          failureCallback
        );
      },
      failureCallback
    );
  },
  failureCallback
);

با این حال، از آنجایی که .then() یک Promise دیگر را برمی‌گرداند، این روش نوشتن دستورالعمل‌های بالا با Promises است:
fetchResource(url)
  .then(handleResult, failureCallback)
  .then(handleNewResult, failureCallback)
  .then(handleAnotherResult, failureCallback);

همانطور که می‌بینید، فراخوانی Promises نیازی به نحو تو در تو ندارد. حتی می‌توانید تکرار callbackهای failure را برای تمیزتر کردن کد حذف کنید، که چیزی است که بخش بعدی مقاله به آن می‌پردازد.
چگونه خطاها را در یک Promise مدیریت کنیم
برای مدیریت خطاها در Promises، از متد .catch() استفاده کنید. اگر مشکلی در هر یک از Promises شما پیش بیاید، این متد می‌تواند دلیل آن خطا را دریافت کند.
Promise.reject(new Error()).catch((reason) => console.error(reason));
// Error

این بار در مثال ما، خروجی دیگر 'uncaught' نیست زیرا .catch().
همچنین می‌توانید از متد .catch() در یک زنجیره از Promises استفاده کنید. این متد اولین خطایی را که در زنجیره با آن روبرو می‌شود، دریافت می‌کند.
به عنوان مثال، بازسازی زنجیره Promises پس از تابع fetchResource() از مثال بخش قبلی. این روش برای جلوگیری از تکرار callbackهای خطا در کد شما است.
fetchResource(url)
  .then(handleResult)
  .then(handleNewResult)
  .then(handleAnotherResult)
  .catch(failureCallback);

همچنین می‌توانید از .catch() برای بررسی خطاها در یک گروه از Promises قبل از ادامه عملیات‌های ناهمزمان بیشتر استفاده کنید.
fetchResource(url)
  .then(handleResult)
  .then(handleNewResult)
  .catch(failureCallback)
  // بررسی خطاها در گروه Promises بالا قبل از ادامه
  .then(handleAnotherResult);

متد .catch() به شما کمک می‌کند تا هرگونه خطا در یک Promise را بدون نیاز به تو در تو کردن توابع callback خطا، مدیریت کنید.
همچنین می‌توانید برای اجرای یک عملیات بدون توجه به اینکه Promise حل شده است یا خیر، از متد .finally() استفاده کنید. متد .then() روشی برای رسیدگی به نتایج یک Promise با نوشتن شرایط جداگانه برای هر دو حالت محقق شده و رد شده است. .catch() فقط زمانی اجرا می‌شود که خطا وجود داشته باشد. اما گاهی اوقات ممکن است بخواهید عملیاتی را اجرا کنید مهم نیست که Promises قبلی چه اتفاقی برای آن‌ها افتاده است.
استفاده از finally() به جلوگیری از تکرار کد احتمالی در .then() و .catch() کمک می‌کند. این برای عملیاتی است که باید اجرا کنید مهم نیست چه اتفاقی می‌افتد.
fetchResource(url)
  .then(handleResult)
  .then(handleNewResult)
  .finally(onFinallyHandle);

متد finally() موارد استفاده‌ای در برنامه‌های وب Front-End دارد. مهم است اگر می‌خواهید عملیات پاکسازی را برای فعالیت‌هایی که Promise آغاز کرده است، انجام دهید. مورد استفاده دیگر - در برنامه‌های وب Front-End - به‌روزرسانی رابط کاربری مانند متوقف کردن یک چرخش بارگذاری است.
چگونه چندین Promise را به طور همزمان مدیریت کنیم
امکان اجرای بیش از یک Promise در یک زمان وجود دارد. تمام مثال‌هایی که تا کنون دیده‌اید برای Promises هستند که یکی پس از دیگری اجرا می‌شوند.
در مثال‌های قبلی، Promises شبیه به کد همزمان اجرا می‌شوند به این معنی که منتظر می‌مانند تا Promise قبلی حل یا رد شود. اما می‌توانید Promises متعددی داشته باشید که به طور موازی اجرا می‌شوند.
در اینجا روش‌های موجود برای دستیابی به این هدف آمده است:

Promise.all()
Promise.race()
Promise.any()
Promise.allSettled()

در این بخش از مقاله، این روش‌ها را بررسی خواهیم کرد.
متد Promise.all()
Promise.all() یک آرایه از Promises را به عنوان آرگومان می‌پذیرد اما یک Promise واحد را به عنوان خروجی برمی‌گرداند. Promise واحدی که برمی‌گرداند با یک آرایه از مقادیر حل می‌شود اگر همه Promises در آرایه ورودی محقق شوند. آرایه‌ای که Promise.all() با آن حل می‌شود، حاوی مقادیر حل شده Promises فردی در آرایه ورودی است.
const promise1 = Promise.resolve(`First Promise's Value`);
const promise2 = new Promise((resolve) =>
  setTimeout(resolve, 3000, `Second Promise's Value`)
);
const promise3 = new Promise((resolve) =>
  setTimeout(resolve, 2000, `Third Promise's Value`)
);

Promise.all([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<fulfilled>: Array(3)}*

Promise.all([promise1, promise2, promise3]).then((values) => {
  values.forEach((value) => console.log(value));
});

// خروجی در کنسول

// First Promise's Value
// Second Promise's Value
// Third Promise's Value

اگر حداقل یک Promise در آرایه ورودی حل نشود، Promise.all() یک Promise رد شده را با دلیلی برمی‌گرداند. دلیل رد شدن، همان دلیل اولین Promise رد شده در آرایه ورودی خواهد بود.
const promise1 = Promise.resolve(`First Promise's Value`);
const promise2 = new Promise((resolve, reject) =>
  setTimeout(reject, 2000, `First reason for rejection`)
);
const promise3 = new Promise((resolve, reject) =>
  setTimeout(reject, 3000, `Second reason for rejection`)
);

Promise.all([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<rejected>: "First reason for rejection"}*

Promise.all() همه Promises ورودی را اجرا می‌کند قبل از اینکه مقداری را برگرداند. اما آن‌ها را یکی پس از دیگری اجرا نمی‌کند - در عوض آن‌ها را به طور همزمان اجرا می‌کند.
به همین دلیل، زمان کلی که طول می‌کشد تا Promise.all() مقداری را برگرداند تقریباً برابر با زمانی است که طول می‌کشد طولانی‌ترین Promise در آرایه به پایان برسد.

با این وجود، باید همه Promises را اجرا کند قبل از اینکه چیزی را برگرداند.
متد Promise.race()
Promise.race() یک آرایه از Promises را به عنوان آرگومان می‌پذیرد و یک Promise واحد را به عنوان خروجی برمی‌گرداند. Promise واحدی که برمی‌گرداند سریع‌ترین Promise برای اتمام اجرا است - حل شده یا رد شده. این بدان معناست که Promise.race() سریع‌ترین Promise را برای اتمام برمی‌گرداند.
const promise1 = new Promise((resolve) =>
  setTimeout(resolve, 3000, `First Promise's Value`)
);
const promise2 = new Promise((resolve) =>
  setTimeout(resolve, 2000, `Second Promise's Value`)
);
const promise3 = Promise.resolve(`Third Promise's Value`);

Promise.race([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<fulfilled>: "Third Promise's Value"}*

در مثال بالا، از آنجایی که promise3 یک Promise است که هنگام ایجاد حل می‌شود، Promise.race() آن را به عنوان سریع‌ترین برمی‌گرداند. درست مانند سایر متدهای Promise که مقاله در این بخش در مورد آن‌ها بحث می‌کند، آن‌ها Promises را به طور موازی اجرا می‌کنند و نه یکی پس از دیگری.
اگر سریع‌ترین Promise برای اتمام به طور تصادفی رد شود، Promise.race() یک Promise رد شده را برمی‌گرداند و دلیل رد شدن را نیز برمی‌گرداند.
const promise1 = Promise.reject(`Reason for rejection`);
const promise2 = new Promise((resolve) =>
  setTimeout(resolve, 3000, `First resolved Promise`)
);
const promise3 = new Promise((resolve) =>
  setTimeout(resolve, 2000, `Second resolved Promise`)
);

Promise.race([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<rejected>: "Reason for rejection"}*

Promise.race() برای اجرای لیستی از عملیات‌های ناهمزمان مفید است اما فقط به نتیجه سریع‌ترین عملیات اجرا شده نیاز دارید.
متد Promise.any()
Promise.any() یک آرایه از Promises را به عنوان آرگومان می‌پذیرد اما یک Promise واحد را به عنوان خروجی برمی‌گرداند. Promise واحدی که برمی‌گرداند اولین Promise حل شده در آرایه ورودی است. این متد منتظر می‌ماند تا هر Promise در آرایه حل شود و بلافاصله آن را به عنوان خروجی برمی‌گرداند.
const promise1 = new Promise((resolve) =>
  setTimeout(resolve, 3000, `First Promise's Value`)
);
const promise2 = new Promise((resolve) =>
  setTimeout(resolve, 2000, `Second Promise's Value`)
);
const promise3 = Promise.reject(`Third Promise's Value`);

Promise.any([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<fulfilled>: "Second Promise's Value"}*

در مثال بالا، promise1 پس از 3 ثانیه حل می‌شود، promise2 پس از 2 ثانیه حل می‌شود و promise3 بلافاصله رد می‌شود. از آنجایی که Promise.any() به دنبال اولین Promise موفقیت‌آمیز است، promise2 را برمی‌گرداند. promise1 کمی دیر است و بنابراین پشت سر گذاشته می‌شود.
اگر هیچ یک از Promises در آرایه حل نشوند، Promise.any() یک Promise رد شده را برمی‌گرداند. این Promise رد شده حاوی یک آرایه جاوا اسکریپت از دلایل است که هر دلیل مربوط به یک Promise از آرایه ورودی است.
const promise1 = new Promise((resolve, reject) =>
  setTimeout(reject, 3000, `First rejection reason`)
);
const promise2 = new Promise((resolve, reject) =>
  setTimeout(reject, 2000, `Second rejection reason`)
);
const promise3 = Promise.reject(`Third rejection reason`);

Promise.any([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<rejected>: Aggregate Error: All Promises were rejected}*

Promise.any([promise1, promise2, promise3]).catch(({ errors }) =>
  console.log(errors)
);

// خروجی در کنسول

// *(3) ["First* rejection reason*", "Second* rejection reason*", "Third* rejection reason*"]*

این متد برای عملیات‌های ناهمزمان مفید است که در آن فقط به نتیجه سریع‌ترین Promise موفقیت‌آمیز نیاز دارید. Promise.any() و Promise.race() مشابه هستند، به جز اینکه Promise.any() سریع‌ترین Promise را برای اتمام و حل شدن برمی‌گرداند، در حالی که Promise.race() سریع‌ترین Promise را برای اتمام برمی‌گرداند و اهمیتی نمی‌دهد که حل شده باشد یا خیر.

متد Promise.allSettled()
Promise.allSettled() یک ویژگی از Promises جاوا اسکریپت شد که با انتشار ES2020 ارائه شد. این کار Promises را به صورت موازی مانند سایر متدهای Promise که مقاله در این بخش در مورد آن‌ها بحث می‌کند، مدیریت می‌کند.
Promise.allSettled() به شما کمک می‌کند تا کد ناهمزمان کارآمدتری بنویسید زیرا نمای کلی از نتیجه همه Promises در آرایه را نشان می‌دهد، صرف نظر از وضعیت - حل شده یا رد شده.
Promise.allSettled() یک آرایه از Promises را به عنوان آرگومان می‌پذیرد و یک Promise واحد را به عنوان خروجی برمی‌گرداند.
Promise واحدی که برمی‌گرداند همیشه حل می‌شود یا به حالت "محقق شده" می‌رود بعد از اینکه همه Promises ورودی حل شوند. اهمیتی نمی‌دهد که آیا هر Promise فردی در آرایه ورودی رد شده است یا خیر. آرایه‌ای که Promise.all() با آن حل می‌شود، حاوی مقادیر حل شده یا دلایل رد شدن Promises در آرایه ورودی است.
const promise1 = new Promise((resolve) =>
  setTimeout(resolve, 3000, `First Promise's Value`)
);
const promise2 = new Promise((resolve) =>
  setTimeout(resolve, 2000, `Second Promise's Value`)
);
const promise3 = Promise.reject(`Third Promise's Value`);

Promise.allSettled([promise1, promise2, promise3]);

// خروجی در کنسول

// *Promise {<fulfilled>: Array(3)}*

Promise.allSettled([promise1, promise2, promise3]).then(console.log);

// خروجی در کنسول

/*
(3) [{…}, {…}, {…}]
0: {status: 'fulfilled', value: "First Promise's Value"}
1: {status: 'fulfilled', value: "Second Promise's Value"}
2: {status: 'rejected', reason: "Third Promise's Value"}
*/

از مثال بالا می‌توانید ببینید که حتی اگر promise3 هنگام ایجاد رد شود، Promise.allSettled() همچنان یک Promise "محقق شده" را برمی‌گرداند. این کار را حتی اگر همه Promises در آرایه ورودی رد شوند، انجام می‌دهد.
Promise.allSettled() شبیه به Promise.all() است در این که همه Promises ورودی آن‌ها باید حل شوند قبل از اینکه Promise که برمی‌گردانند یک حالت حل شده یا رد شده داشته باشد.
تفاوت این است که Promise.all() فقط می‌تواند در صورتی موفق شود که همه Promises در ورودی حل شوند، در حالی که Promise.allSettled() اهمیتی نمی‌دهد که وضعیت Promises ورودی چگونه باشد.

استفاده از این متد به شما یک نمای کلی از نحوه عملکرد همه Promises شما می‌دهد، آن‌هایی که حل شده‌اند و آن‌هایی که رد شده‌اند. این اطلاعات کاملی در مورد همه Promises شما ارائه می‌دهد و به شما امکان می‌دهد آن‌ها را به طور مستقل بررسی کنید - نتیجه یکی بر وضعیت Promise که متد برمی‌گرداند تأثیر نمی‌گذارد.
نحو Async/Await چیست؟
نحو async/await یک ویژگی جاوا اسکریپت شد که با انتشار ES8 (ES2017) ارائه شد. این بر اساس Promises ساخته شده است و می‌توانید آن را به عنوان یک نحو جایگزین برای Promises در نظر بگیرید.
async/await زنجیره‌سازی را که در نحو Promises رایج است، از بین می‌برد و کد ناهمزمان را بسیار همزمان‌تر می‌کند.
Promises راهی عالی برای جلوگیری از "هرم نفرین callback" قبلی هستند، اما async/await کد ناهمزمان را یک گام جلوتر می‌برد. با async/await، کد خواناتر و نگهداری آن آسان‌تر است. این روش برای بهبود خوانایی کد برای عملیات‌های ناهمزمان ایجاد شده است. این روش مدرن برای کار با Promises است.
چگونه یک تابع Async در جاوا اسکریپت ایجاد کنیم
async یک کلمه کلیدی جاوا اسکریپت است که برای ایجاد یک تابع استفاده می‌شود. تابعی که این کلمه کلیدی به ایجاد آن کمک می‌کند، همیشه یک Promise را برمی‌گرداند. برای استفاده از آن، async را قبل از کلمه کلیدی function هنگام تعریف تابع قرار دهید.
async function example() {
    // برگرداندن یک مقدار
}

example()

// خروجی در کنسول

// *Promise {<fulfilled>: undefined}*

از نمونه کد، می‌توانید ببینید که تابع یک Promise با مقدار undefined را برمی‌گرداند. این به این دلیل است که هر چیزی که تابع async برمی‌گرداند، مقدار حل شده Promise حاصل خواهد بود. در این مورد، تابع چیزی را برنمی‌گرداند، بنابراین undefined.
async function example() {
  return "Feels good to be an async function";
}

example();

// خروجی در کنسول

// *Promise {<fulfilled>: "Feels good to be an async function"}*

در مثال بالا، تابع یک رشته را برمی‌گرداند که به عنوان مقدار حل شده Promise می‌شود. این روش ایجاد یک تابع async است.
چگونه از کلمه کلیدی Await استفاده کنیم
برای استفاده از کلمه کلیدی await، آن را قبل از یک Promise قرار دهید. این نشان‌دهنده این است که تابع async باید اجرای خود را تا زمانی که Promise حل شود متوقف کند.
این شبیه به متد .then() است که تضمین می‌کند یک Promise "محقق شده" یا "رد شده" است قبل از ادامه. توجه داشته باشید که فقط می‌توانید از کلمه کلیدی await در داخل یک تابع async استفاده کنید.
به جای زنجیر کردن Promises با .then() همانطور که مقاله قبلاً آموزش می‌دهد، می‌توانید عملیات‌های ناهمزمان را به طور مکرر با await منتظر بمانید و کد خود را تمیزتر و خواناتر کنید.
const timerPromise = (message) =>
  new Promise((resolve) => setTimeout(resolve, 3000, message));

async function asyncFunc() {
  const result = await timerPromise("promise finished!");
  console.log(result);
}

// خروجی در کنسول پس از 3 ثانیه

// promise finished!

استفاده از کلمه کلیدی await قبل از یک Promise، مقدار حل شده آن Promise را تولید می‌کند. واضح است که از خط const result = await promise('promise finished!') که در آن result یک رشته است و نه یک Promise جدید. این با .then() متفاوت است که همیشه یک Promise جدید را برمی‌گرداند.
با await، می‌توانید هر زنجیره Promises را بشکنید و مقادیر حل شده آن‌ها را دریافت کنید. مثال زیر از تابع fetch() - که یک Promise است - برای نشان دادن حذف زنجیره‌سازی با async/await استفاده می‌کند.
// با زنجیره‌سازی
fetch("https://jsonplaceholder.typicode.com/users")
  .then((response) => response.json())
  .then((result) => console.log(result));

// خروجی در کنسول

// Array(10) [...]

// بدون زنجیره‌سازی
async function fetchResource(url) {
  const response = await fetch(url);
  const result = await response.json();
  console.log(result);
}
fetchResource("https://jsonplaceholder.typicode.com/users");

// خروجی در کنسول

// Array(10) [...]

در نهایت، این موضوع به ترجیح و انتخاب شما بستگی دارد. اگر نحو زنجیره‌سازی را ترجیح می‌دهید، پس بروید و از آن استفاده کنید. اگر ترجیح می‌دهید کد شما شبیه به کد همزمان به نظر برسد و می‌خواهید از async/await استفاده کنید، آن هم خوب است.
همچنین می‌توانید از هر دو نحو با هم استفاده کنید، Promises را در داخل یک تابع async زنجیر کنید. همه چیز به این بستگی دارد که می‌خواهید به چه چیزی برسید و سبکی که ترجیح می‌دهید.
چگونه خطاها را در Async/Await مدیریت کنیم
مانند نحو Promise معمولی، می‌توانید خطاها را به درستی با استفاده از async/await مدیریت کنید. مدیریت صحیح خطاها در فراخوانی‌های async بسیار مهم است تا اشکالات را ردیابی کنید. برای انجام این کار از بلوک‌های try/catch استفاده کنید.
try یک کلمه کلیدی جاوا اسکریپت است که یک بلوک کد را در بر می‌گیرد. همانطور که آن بلوک کد اجرا می‌شود، try به دنبال خطاها می‌گردد. هیچ خطایی نمی‌تواند از یک بلوک try فرار کند. از try در داخل یک تابع async استفاده کنید.
بلوک catch شبیه به .catch() در Promises است. این یک تابع از یک خطا است.
async function fetchResource(url) {
  try {
    const response = await fetch(url);
    const result = await response.json();
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

در این مثال، کلمه کلیدی catch یک خطا دارد که به کنسول ثبت می‌شود. یک Promise حل شده با یک خطای بدون رسیدگی، یک Promise رد شده را نتیجه می‌دهد. اطمینان حاصل کنید که کد خود را در بلوک‌های try/catch قرار می‌دهید تا کنترل بیشتری بر خرابی‌ها و خطاها در برنامه خود داشته باشید.
همچنین، مانند متد .finally() برای Promises، می‌توانید از یک بلوک finally در داخل یک تابع async استفاده کنید. پرانتزهایی که این کلمه کلیدی را دنبال می‌کنند، یک بلوک کد را در بر می‌گیرند که صرف نظر از وجود خطا یا عدم وجود آن اجرا می‌شود.
async function fetchResource(url) {
  try {
    const response = await fetch(url);
    const result = await response.json();
    console.log(result);
  } catch (error) {
    console.error(error);
  } finally {
    console.log("Operation finished!");
  }
}

استفاده از بلوک finally موارد استفاده‌ای در برنامه‌های وب واقعی دارد. مهم است اگر می‌خواهید عملیات پاکسازی را برای فعالیت‌هایی که Promise آغاز کرده است، انجام دهید. مورد استفاده دیگر - در برنامه‌های وب Front-End - به‌روزرسانی رابط کاربری مانند متوقف کردن یک چرخش بارگذاری است.
صف کار چیست؟
صف کار - که به عنوان صف Microtask نیز شناخته می‌شود - یک مفهوم مهم است که باید در جاوا اسکریپت از آن آگاه باشید. این در اصل یک جزء از زمان اجرای جاوا اسکریپت نبود، اما نیاز به آن با ظهور Promises در جاوا اسکریپت ایجاد شد.
کد نمونه زیر را در نظر بگیرید:
Promise.resolve("This is a resolved value").then(console.log);
setTimeout(console.log, 0, "This is a value after the timeout");
console.log("This is a normal log");

در اینجا خط اول یک Promise است که به طور خودکار حل می‌شود، سپس مقداری را در کنسول ثبت می‌کند. خط دوم یک timeout است که به 0 میلی ثانیه تنظیم شده است که به این معنی است که قرار است فوری باشد. timeout یک تابع callback را می‌گیرد که مقداری را در کنسول ثبت می‌کند. خط سوم یک log کنسول معمولی است.
وقتی برنامه را اجرا می‌کنید، می‌توانید حدس بزنید که این logها به چه ترتیبی ظاهر می‌شوند؟ بیایید بفهمیم.
// خروجی در کنسول
/*
This is a normal log
This is a resolved value
--
undefined
--
This is a value after the timeout
*/

این یک خروجی جالب است. اولین log از console.log است. این خیلی گیج کننده نیست زیرا console.log() یک عملیات async نیست. موتور جاوا اسکریپت به طور فعال هر دستور همزمان را بلافاصله پس از شروع برنامه اجرا می‌کند.
خط دوم ممکن است کمی گیج کننده باشد. این مقدار حل شده Promise را ثبت می‌کند. چرا خروجی Promise بعد از آن می‌آید؟ خوب، پاسخ ساده این است که یک Promise سریع‌تر از هر پیاده‌سازی async دیگری در جاوا اسکریپت است. اما این تمام داستان نیست.
در زمان اجرای جاوا اسکریپت، حلقه رویداد عملیات‌های async را مدیریت می‌کند. این می‌تواند توابع callback عملیات‌های async را فقط زمانی فراخوانی کند که پشته فراخوانی خالی باشد. حل یک Promise یک عملیات ناهمزمان است و قابل درک است که بعد از یک log معمولی می‌آید. اما چرا قبل از دستور setTimeout() می‌آید؟
زمان اجرای جاوا اسکریپت در واقع دارای دو صف است - صف Callback (یا Macrotask) و صف کار (یا Microtask). درست قبل از اینکه حلقه رویداد شروع به فراخوانی توابع در صف Callback کند، همه دستورالعمل‌ها را در صف کار فراخوانی می‌کند. callback یک Promise در صف کار می‌ماند تا حلقه رویداد آن را ابتدا فراخوانی کند. به همین دلیل است که Promises مقادیر را سریع‌تر از هر پیاده‌سازی async دیگری برمی‌گردانند.
صف کار برای برخی از دستورالعمل‌های دیگر به جز Promises نیز مفید است. با این حال، این فراتر از دامنه این مطالب است. اگر کنجکاو هستید، می‌توانید در اینجا در مورد صف کار بیشتر بخوانید.
یک برنامه بلافاصله پس از مراقبت از صف کار برمی‌گردد. از مثال بالا، آن را با undefined برمی‌گرداند. پس از آن، حلقه رویداد به صف Callback می‌رود و دستورالعمل‌های آنجا را اجرا می‌کند.

تصویر اعتبار: Saravanakumar در Medium
callback در setTimeout() همیشه به صف Callback منتقل می‌شود مهم نیست که تایمر چقدر کوتاه باشد. در مثال، حتی با وجود اینکه تایمر به 0 میلی ثانیه تنظیم شده بود، آخرین ثبت را انجام داد.
با این، امیدوارم که درک کرده باشید که چرا کد نمونه خروجی آن را تولید کرد و تفاوت بین صف Callback و صف Microtask چیست.
نتیجه‌گیری
این یک بررسی عمیق از Promises و عملیات‌های async بود. در این مقاله، یاد گرفتید که Promises چگونه در جاوا اسکریپت به وجود آمدند، آن‌ها چه هستند و چگونه آن‌ها را ایجاد کنید.
همچنین یاد گرفتید که چگونه یک callback به یک Promise متصل کنید، چگونه خطاها را در یک Promise مدیریت کنید و چگونه چندین Promise را به طور همزمان مدیریت کنید.
ما همچنین نحو async/await را بررسی کردیم که بر اساس Promises ساخته شده است. شما یاد گرفتید که چه زمانی یک ویژگی جاوا اسکریپت شد، چگونه یک تابع async ایجاد کنید و چگونه از کلمه کلیدی await استفاده کنید.
شما همچنین یاد گرفتید که چگونه خطاها را با استفاده از نحو مدیریت کنید. در نهایت، مقاله توضیح داد که صف کار چیست.
اگر همه چیز را یکباره درک نکردید، آزادانه برگردید. Promises جاوا اسکریپت ممکن است یادگیری و تسلط بر آن زمان ببرد، اما هر توسعه‌دهنده جاوا اسکریپت از دانستن نحوه عملکرد Promises بسیار سود می‌برد. این به شما کنترل بیشتری می‌دهد در حالی که کد async حرفه‌ای را برای برنامه‌های خود می‌نویسید.
موفق باشید در ساخت پروژه بعدی خود.
PS: من را در توییتر و لینکدین دنبال کنید.

به صورت رایگان کدنویسی یاد بگیرید. برنامه درسی منبع باز freeCodeCamp به بیش از 40000 نفر کمک کرده است تا شغل‌هایی به عنوان توسعه‌دهنده به دست آورند. شروع کنید


سلام دوستان! در این مقاله می‌خواهم به شما نشان دهم که چگونه هنگام کار با Promise های جاوااسکریپت، از نحوه‌ی خاص "async/await" استفاده کنید.
اگر با Promise های جاوااسکریپت آشنا نیستید یا نیاز به یادآوری دارید، می‌توانید مقاله‌ی قبلی من را بخوانید: نحوه‌ی کار Promise های جاوااسکریپت – آموزش برای مبتدیان.
قبل از یادگیری نحوه‌ی async/await، باید درک خوبی از Promise های جاوااسکریپت داشته باشید.
نحوه‌ی async/await یک نحوه‌ی خاص است که برای کمک به شما در کار با اشیاء Promise ایجاد شده است. این نحوه‌ی کد شما را تمیزتر و واضح‌تر می‌کند.
هنگام کار با یک Promise، باید فراخوانی تابع یا متغیری که یک Promise را برمی‌گرداند را با استفاده از متدهای then/catch زنجیر کنید.
وقتی Promise های زیادی دارید، به زنجیره‌های زیادی از متد then نیز نیاز خواهید داشت. برای مثال، در اینجا نحوه بازیابی داده‌ها با استفاده از تابع fetch() آمده است:
fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(error => console.log(error));

یک پیاده‌سازی معمولی API Fetch
در کد بالا، تابع fetch() یک Promise را برمی‌گرداند که ما با استفاده از متد then() آن را مدیریت می‌کنیم. در داخل اولین متد then()، پاسخ درخواست را می‌پذیریم و آن را با استفاده از متد json() به یک شیء تبدیل می‌کنیم.
در متد then() دوم، داده‌های json برگشتی از فراخوانی متد json() را دریافت می‌کنیم و سپس آن داده‌ها را در کنسول ثبت می‌کنیم.
ما همچنین متد catch() را اضافه می‌کنیم تا هر خطایی را که ممکن است هنگام اجرای درخواست رخ دهد، مدیریت کنیم.
درک کد واقعاً دشوار نیست، اما می‌توانیم آن را حتی زیباتر کنیم با حذف زنجیره‌های .then() و .catch() که توابع callback را نیز حذف می‌کند.
کلیدواژه‌ی Await
کلیدواژه‌ی await اساساً باعث می‌شود جاوااسکریپت تا زمانی که شیء Promise حل یا رد شود، منتظر بماند. به جای اینکه مجبور باشید از الگوی callback در داخل متد then() استفاده کنید، می‌توانید Promise حل شده را به یک متغیر اختصاص دهید، مانند این:
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const json = await response.json();
console.log(json)

استفاده از Await به جای زنجیره‌سازی متد .then()
کلیدواژه‌ی await قبل از فراخوانی تابعی یا متغیری که یک Promise را برمی‌گرداند، قرار می‌گیرد. این کلیدواژه باعث می‌شود جاوااسکریپت منتظر بماند تا شیء Promise حل شود قبل از اجرای کد در خط بعدی.
حال اگر کد بالا را اجرا کنید، ممکن است با خطایی مانند این مواجه شوید:
SyntaxError: await is only valid in async functions and the top level bodies of modules

خطای نحوی هنگام استفاده از Await خارج از توابع ناهمزمان
این خطا به این دلیل رخ می‌دهد که کلیدواژه‌ی await باید در داخل یک تابع ناهمزمان یا بدنه اصلی یک ماژول استفاده شود.
کلیدواژه‌ی Async
برای ایجاد یک تابع ناهمزمان، باید کلیدواژه‌ی async را قبل از نام تابع خود اضافه کنید. به خط 1 در مثال زیر نگاه کنید:
async function runProcess() {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  const json = await response.json();
  console.log(json)
}

runProcess();

ایجاد یک تابع ناهمزمان
در اینجا، ما یک تابع ناهمزمان به نام runProcess() ایجاد کردیم و کدی را که از کلیدواژه‌ی await استفاده می‌کند، در داخل آن قرار دادیم. سپس می‌توانیم تابع ناهمزمان را با فراخوانی آن، درست مانند یک تابع معمولی، اجرا کنیم.
چگونه خطاها را در Async/Await مدیریت کنیم
برای مدیریت خطایی که ممکن است از نحوه‌ی async/await رخ دهد، می‌توانید از بلوک try/catch برای گرفتن هر رد شدن از Promise های خود استفاده کنید.
مثال زیر را ببینید:
async function runProcess() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const json = await response.json();
    console.log(json);
  } catch (error) {
    console.log(error);
  }
}

runProcess();

اضافه کردن try/catch در یک تابع Async
بلوک try/catch که در داخل تابع runProcess() قرار دارد، رد شدن ناشی از اشیاء Promise را مدیریت می‌کند.
اکنون ما یک نسخه‌ی کامل async/await از کد Promise استاندارد را که قبلاً ایجاد کرده بودیم، داریم. در اینجا مقایسه‌ای بین این دو آورده شده است:

مقایسه‌ی کد Promise در مقابل Async/Await
در نسخه‌ی async/await، نتیجه‌ی Promise مستقیماً به یک متغیر اختصاص داده می‌شود. در نسخه‌ی Promise استاندارد، نتیجه‌ی Promise به عنوان یک آرگومان به متد .then() ارسال می‌شود.
اکثر توسعه‌دهندگان نسخه‌ی async/await را ترجیح می‌دهند زیرا ظاهری ساده‌تر دارد.
چگونه از Async/Await در IIFE و توابع Arrow استفاده کنیم
یک عبارت تابع فراخوانی فوری (IIFE) تکنیکی است که برای اجرای یک تابع بلافاصله پس از تعریف آن استفاده می‌شود.
برخلاف توابع و متغیرهای معمولی، IIFE ها پس از اجرا از فرآیند در حال اجرا حذف می‌شوند.
علاوه بر تابع استاندارد، می‌توانید یک IIFE ناهمزمان نیز ایجاد کنید. این زمانی مفید است که نیاز دارید تابع ناهمزمان را فقط یک بار اجرا کنید:
(async function () {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const json = await response.json();
    console.log(json);
  } catch (error) {
    console.log(error);
  }
})();

مثال تابع ناهمزمان IIFE
همچنین می‌توانید هنگام ایجاد یک تابع ناهمزمان از نحو arrow استفاده کنید، به این صورت:
const runProcess = async () => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const json = await response.json();
    console.log(json);
  } catch (error) {
    console.log(error);
  }
};

runProcess();

مثال تابع ناهمزمان Arrow
در کد خود از نحوه‌ای که می‌خواهید استفاده کنید.
چرا از نحوه‌ی async/await استفاده کنیم؟
نحوه‌ی async/await به شما امکان می‌دهد Promise ها را بدون استفاده از زنجیره‌سازی متدهای .then() و .catch() مدیریت کنید، که همچنین نیاز به callback های تودرتو را از بین می‌برد.
این مزیت زمانی قابل توجه است که یک فرآیند پیچیده پس از حل Promise دارید.
با بازگشت به مثال بالا، فرض کنید یک دستور شرطی در داخل متد .then() دارید، به این صورت:
fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => {
    if (json.userId == 1) {
      json.completed == false;
    } else {
      json.completed == true;
    }
  })
  .catch(error => console.log(error));

یک بلوک if/else در داخل متد then() Promise
در اینجا، می‌بینید که تابع callback که داده‌های json را می‌پذیرد، یک بلوک if/else در داخل آن دارد.
این کد در مقایسه با نسخه‌ی async/await زیر، درک و اصلاح آن دشوارتر است:
(async function () {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const json = await response.json();
    if (json.userId == 1) {
      json.completed == false;
    } else {
      json.completed == true;
    }
    console.log(json);
  } catch (error) {
    console.log(error);
  }
})();

با استفاده از نحوه‌ی async/await، نیاز به زنجیره‌سازی متد و callback های تودرتو را کاهش می‌دهید. این تأثیر بر خوانایی کد شما دارد، به خصوص زمانی که کدهای تودرتو مانند بلوک‌های if/else و حلقه‌ی for دارید.
نتیجه‌گیری
اکنون یاد گرفته‌اید که نحوه‌ی async/await چگونه کار می‌کند. این نحوه‌ی کار با Promise ها را بسیار آسان‌تر می‌کند با حذف نیاز به زنجیره‌سازی متدهای .then() و .catch() که همچنین نیاز به callback های تودرتو را از بین می‌برد.
حتی اگر کلیدواژه‌ی await فقط بتواند در داخل یک تابع ناهمزمان یا یک ماژول استفاده شود، می‌توانید از یک IIFE برای فراخوانی تابع ناهمزمان فقط یک بار استفاده کنید.
اگر از این مقاله لذت بردید و می‌خواهید مهارت‌های جاوااسکریپت خود را به سطح بعدی ببرید، توصیه می‌کنم کتاب جدید من Beginning Modern JavaScript را در اینجا بررسی کنید.

این کتاب طوری طراحی شده است که درک آن آسان باشد و برای هر کسی که به دنبال یادگیری جاوااسکریپت است، قابل دسترس باشد. این کتاب یک راهنمای گام به گام ملایم ارائه می‌دهد که به شما کمک می‌کند تا نحوه استفاده از جاوااسکریپت برای ایجاد یک برنامه پویا را درک کنید.
این قول من است: شما واقعاً احساس خواهید کرد که آنچه را که با جاوااسکریپت انجام می‌دهید، درک می‌کنید.
تا دفعه بعد!

به صورت رایگان کدنویسی یاد بگیرید. برنامه‌ی درسی منبع باز freeCodeCamp به بیش از 40000 نفر کمک کرده است تا به عنوان توسعه‌دهنده مشغول به کار شوند. شروع کنید.