در این نوشته یاد میگیری که چجوری اپلیکیشن‌های Node . js ای که با callback یا Promise نوشتی رو با توابع async ساده‌ترشون کنی. اگه قبلا یه نگاهی به الگوی async / await و promise ها در جاوااسکریپت انداختی ولی هنوز کامل بهشون مسلط نیستی و یا این که فقط نیاز داری تا مرورشون کنی، هدف این نوشته کمک به تویه. تابع‌های async چی هستن؟ توابع async به طور پیش‌فرض در Node در دسترسند و با کلمه‌ی کلیدی علامت‌گذاری میشن. این توابع حتی اگه به طور صریح در بدنشون مشخص نکنی، همیشه یه promise برمیگردونن. در ضمن، فعلا کلمه‌ی کلیدی در داخل توابع async قابل استفاده‌اس و نمیشه در دامنه سراسری ( global scope ) ازش استفاده کرد. داخل یه تابع async میتونی منتظر یه بمونی و یا این که در صورت مردود شدنش ( rejected ) میتونی خطاش رو دستگیر کنی ( catch ) پس اگه یه کدی داری که با promise ها پیاده‌سازی شده: میتونی با شبیه به یه کد همگام ( synchronous ) بنویسیش: در حال حاضر در Node اگه در یه promise خطایی رخ بده که بهش رسیدگی نشده، Node فقط بهت هشدار میده، پس بنابراین نیازی نیست تا خودتو توی دردسر ساختن یه listener بندازی. هرچند چون این اتفاق نشون‌دهنده‌ی یه حالت نامشخصه، توصیه میشه تا اپلیکیشن رو ببندی و کرش کنی دقیقا شبیه به حالتی که یه خطای catch نشده در جایی از کد رخ میده. این کارو میتونی یا با استفاده از پرچم در کامند‌لاین انجام بدی یا با پیاده‌سازی چیزی شبیه به این: قراره تا قابلیت خارج شدن اتوماتیک از پروسه، در نسخه‌های آینده‌ی Node اضافه بشه. این که کدت رو از قبل برای اینکار آماده کنی زحمت زیادی نداره ولی خوبیش اینه که وقتی خواستی نسخه‌ها رو آپدیت کنی دیگه نگران این موضوع نیستی. الگوها با توابع async از اونجایی که رسیدگی به عملیات ناهمگام ( asynchronous ) با استفاده از Promise ها و یا callback ها نیاز به الگوهای پیچیده‌ای داره، وقتی که بتونی این عملیات رو جوری پیاده‌سازی کنی که انگار همگام هستن، کار خیلی برات ساده‌تره. از نسخه‌ی . . نود جی‌اس، پشتیبانی از async iterators و حلقه‌ی مرتبطش یعنی for - await اضافه شده. این امکانات زمانی بدردت میخورن که میخوای روی مقداری که یه iterator method برمیگردونه و از قبل مشخص نیستن، حرکت کنی (حلقه بزنی) و کاری رو انجام بدی و حالت نهایی این تکرار ( iteration ) هم مشخص نیست - معمولا این حالت حین کار با stream ها پیش میاد. تلاش مجدد با عقب‌نشینی نمایی ( exponential backoff ) پیاده‌سازی الگوریتم تلاش مجدد با Promise ها خیلی بدترکیبه: این پیاده‌سازی کاری که میخوایمو میکنه اما میتونیم بازنویسیش کنیم و با خیلی راحت‌تر کارو انجام بدیم: خیلی بیشتر به دل میشینه، نه؟ مقادیر میانی ( intermediate values ) با این که مثال پیش‌رو به اندازه‌ی قبلی ترسناک نیست، اما اگه حالتی داشته باشی که تابع ناهمگام مختلف به شکلی که در زیر توضیح میدم، بهم وابسته باشن، مجبوری تا از بین چندتا راه‌حل زشت و بدترکیب یکیشونو انتخاب کنی. راه حل : درخت کریسمس then . توی این راه حل، برای انجام مقدار رو از کلوژر ( closure ) سومین میگیری و مقدار رو از Promise قبلش که انجام شده. نمیتونی این درخت کریسمس رو مسطح کنی چون در اون صورت کلوژر رو گم میکنی و در دسترس نخواهد بود. راه حل : حرکت به یه اسکوپ ( scope ) بالاتر توی مثال درخت کریسمس شبیه به همین مثال، از یه اسکوپ بالاتر برای دسترسی به استفاده کردیم. اما تفاوت این مثال اینه که متغیر رو خارج از اسکوپ تعریف کردیم تا بتونیم مقدار نهایی اولین Promise رو بهش انتساب بدیم. این مثال قطعا کار میکنه و زنجیره‌ی هم مسطح شده و از نظر معنایی هم درسته. با این وجود اگه از در جاهای دیگه از تابع استفاده کنی، راه برای پیدا شدن باگ‌های جدید باز میشه. علاوه بر این، مجبوری برای یه مقدار یکسان از دوتا اسم مختلف استفاده کنی - و. راه حل : آرایه‌ی غیر ضروری جز این که میخوای درخت رو مسطح کنی، هیچ دلیلی نداره که رو همراه با Promise ای که برمیگردونه داخل یه آرایه پاس بدیم. مقدار این دو عنصر آرایه ممکنه از دو جنس کاملا مختلف باشن و بنابراین جالب نیست که توی یه آرایه‌ی واحد قرار بگیرن. راه حل : یه تابع کمکی بنویس البته که میتونی یه تابع کمکی بنویسی تا این آش شله قلمکار رو درست کنی. اما از نظر خوانایی خیلی ضعیفه و بنابراین ممکنه درکش برای کسایی که توی برنامه‌نویسی functional موهاشون سفید نشده، سخت باشه. با استفاده از به طور معجزه‌آسایی مشکلاتمون ناپدید میشه: چندین درخواست موازی با async / await این مثال شبیه قبلیه. فرض کن میخوای چند کار ناهمگام مختلف رو در یک لحظه شروع کنی و از مقادیر برگشتیشون تو جاهای مختلف استفاده کنی: متدهای iteration آرایه اگرچه رفتارشون خیلی غیرمنتظرست ولی میتونی، و رو با توابع async استفاده کنی. تلاش کن حدس بزنی که خروجی اسکریپت‌های زیر چیه: . map . filter . reduce . . . اگه خروجی هرکدوم از promise هایی که برمیگردونه رو چاپ کنی، میبینی که نتیجه‌ای که مورد انتظاره میده. تنها مشکل اینه که هرکدوم از این مقادیر توسط توی یه Promise بسته‌بندی شدن. پس اگه میخوای مقادیرتو بگیری، باید آرایه‌ی برگشتی رو به پاس بدی: بدون باید اول منتظر تموم شدن promise ها میموندی و بعد روی مقادیرشون رو اجرا میکردی: روشی که از استفاده میکنه زمانی که برای هر مقدار promise نیازه تا کارهای همگام طولانی بکنی و در عین حال کارهای ناهمگام هم طولانی‌اند، میتونه بدردت بخوره. با این روش، به محض این که اولین مقدار رو بدست بیاری، میتونی محاسبتش رو شروع کنی و مجبور نیسی برای شروع محاسباتت منتظر بقیه‌ی Promise ها بمونی تا تکمیل بشن. اگرچه خروجی نهایی مقادیر توی Promise ها پیچیده شدن، اما این Promise ها خیلی سریع تکمیل میشن و در نهایت کل فرآیند سریع‌تر از زمانیه که بخوای به طور متوالی انجامش بدی. خب، درست حدس زدی: اگرچه مقادیر برگشتی از این قراره: اما هرکدومشون توی یه Promise پیچیده شدن، و از اونجایی که هر Promise یه مقدار اصطلاحا truthy هست (یعنی زمانیه که به شکل بولین ببینیمش، مقدارش میشه) ، تمام مقادیر آرایه برمیگردن و هیچکدوم فیلتر نمیشن. متاسفانه تنها کاری که برای رفع این مشکل از دستت برمیاد اینه که اول منتظر بشی تا مقادیر Promise ها برگردن و بعد فیلترشون کنی. متد خیلی سرراسته. اگرچه یادت باشه که باید مقدار اولیه رو داخل بپیچی، و همچنین مقدار متغیر تجمعی هم به صورت Promise برمیگرده و بنابراین نیازه تا براش از استفاده کنی. بازنویسی اپلیکیشن‌های برپایه‌ی callback توابع async به طور پیشفرض یه برمیگردونن، بنابراین میتونی هر تابعی که برپایه‌ی callback هست رو جوری بازنویسی کنی که از Promise ها استفاده کنه و سپس مقدار بازگشتیش رو با بگیری. توی Node . js برای تبدیل یه تابع برپایه‌ی callback به یه تابع برپایه‌ی Promise میتونی از تابع استفاده کنی. بازنویسی اپلیکیشن‌های برپایه‌ی Promise زنجیره‌های ساده‌ی رو خیلی سرراست میشه با استفاده از بازنویسی کرد. تبدیل میشه به: اپلیکیشن‌های Node . js رو با بازنویسی کن اگه: مفاهیم قدیمی و باحالی مثل شرط‌های و حلقه‌های رو دوس داری. باور داری که بلوک‌های راه درست رسیدگی به خطاهاست. همونطور که باهم دیدیم، استفاده از هم خوانایی کدهارو بیشتر میکنه و هم نوشتنشون رو ساده‌تر میکنه و در بسیاری از کارها مناسب‌تر از زنجیره‌های هست. اما اگه به شور و اشتیاقی که در چند سال اخیر برای برنامه‌نویسی فانکشنال بوجود اومده دچار شدی، شاید بهتر باشه که از خیر این قابلیت جاوااسکریپت بگذری. منبع: از وبلاگ RisingStack