آموزش جاوا اسکریپت پیشرفته : جاوا اسکریپت ناهمزمان
در این مقاله، توضیح خواهیم داد که برنامهنویسی ناهمگام چیست، چرا به آن نیاز داریم و بهطور خلاصه برخی از روشهای پیادهسازی توابع ناهمگام در جاوااسکریپت را بررسی خواهیم کرد.
برنامهنویسی ناهمگام تکنیکی است که برنامه شما را قادر میسازد تا یک وظیفه بالقوه طولانیمدت را شروع کند و همچنان بتواند در حین اجرای آن وظیفه به رویدادهای دیگر پاسخگو باشد، به جای اینکه مجبور باشد تا پایان آن وظیفه منتظر بماند. پس از پایان آن وظیفه، نتیجه به برنامه شما ارائه میشود.
بسیاری از توابع ارائه شده توسط مرورگرها، به ویژه جالبترین آنها، میتوانند به طور بالقوه زمان زیادی طول بکشند و بنابراین ناهمگام هستند. به عنوان مثال:
ایجاد درخواستهای HTTP با استفاده از
دسترسی به دوربین یا میکروفون کاربر با استفاده از
درخواست از کاربر برای انتخاب فایلها با استفاده از
بنابراین، اگرچه ممکن است مجبور نباشید توابع ناهمگام خود را اغلب پیادهسازی کنید، اما به احتمال زیاد نیاز به استفاده صحیح از آنها خواهید داشت.
در این مقاله، با بررسی مشکل توابع همگام با اجرای طولانیمدت شروع خواهیم کرد، که برنامهنویسی ناهمگام را به یک ضرورت تبدیل میکند.
این کد:
رشتهای به نام name را تعریف میکند.
رشته دیگری به نام greeting را تعریف میکند که از name استفاده میکند.
خوشامدگویی را به کنسول جاوا اسکریپت ارسال میکند.
در اینجا باید توجه داشته باشیم که مرورگر به طور مؤثر برنامه را به ترتیبی که نوشتهایم، خط به خط اجرا میکند. در هر مرحله، مرورگر منتظر میماند تا خط کار خود را تمام کند و سپس به خط بعدی میرود. مرورگر باید این کار را انجام دهد زیرا هر خط به کار انجام شده در خطوط قبلی بستگی دارد.
این باعث میشود این یک برنامه همگام باشد. حتی اگر یک تابع جداگانه مانند این را فراخوانی کنیم، همچنان همگام خواهد بود:
در اینجا، یک تابع همگام (synchronous) است زیرا فراخواننده باید منتظر بماند تا تابع کارش را تمام کند و مقداری را برگرداند تا بتواند ادامه دهد.
یک تابع همگام با اجرای طولانی مدت
اگر تابع همگامسازی مدت زیادی طول بکشد چه؟
برنامه زیر از یک الگوریتم بسیار ناکارآمد برای تولید چندین عدد اول بزرگ هنگام کلیک کاربر بر روی دکمه "تولید اعداد اول" استفاده میکند. هرچه تعداد اعداد اولی که کاربر مشخص میکند بیشتر باشد، عملیات بیشتر طول خواهد کشید.
روی «تولید اعداد اول» کلیک کنید. بسته به سرعت رایانه شما، احتمالاً چند ثانیه طول میکشد تا برنامه پیام «پایان یافت!» را نمایش دهد.
مشکل توابع همگام با اجرای طولانی مدت
مثال بعدی درست مانند مثال قبلی است، با این تفاوت که یک کادر متنی برای تایپ شما اضافه کردهایم. این بار، روی «تولید اعداد اول» کلیک کنید و بلافاصله پس از آن در کادر متنی تایپ کنید.
متوجه خواهید شد که در حالی که تابع در حال اجرا است، برنامه ما کاملاً بیپاسخ است: شما نمیتوانید چیزی تایپ کنید، روی چیزی کلیک کنید یا کار دیگری انجام دهید.
دلیل این امر این است که این برنامه جاوا اسکریپت تکرشتهای است. یک رشته، دنباله ای از دستورالعملهایی است که یک برنامه از آنها پیروی میکند. از آنجا که برنامه از یک رشته تشکیل شده است، فقط میتواند یک کار را در یک زمان انجام دهد: بنابراین اگر منتظر بازگشت فراخوانی همگام طولانی مدت ما باشد، نمیتواند کار دیگری انجام دهد.
آنچه ما نیاز داریم، راهی برای برنامه ما است تا:
با فراخوانی یک تابع، یک عملیات طولانی مدت را شروع کند.
آن تابع عملیات را شروع کند و بلافاصله آن را برگرداند، به طوری که برنامه ما همچنان بتواند به سایر رویدادها پاسخگو باشد.
تابع عملیات را به گونهای اجرا کند که رشته اصلی را مسدود نکند، به عنوان مثال با شروع یک رشته جدید.
پس از اتمام عملیات، نتیجه آن را به ما اطلاع دهد.
این دقیقاً همان کاری است که توابع ناهمزمان ما را قادر به انجام آن میکند. بقیه این ماژول نحوه پیادهسازی آنها در جاوا اسکریپت را توضیح میدهد.
توضیحی که در مورد توابع ناهمزمان دیدیم ممکن است شما را به یاد کنترلکنندههای رویداد بیندازد، و اگر اینطور باشد، حق با شماست. کنترلکنندههای رویداد در واقع نوعی برنامهنویسی ناهمزمان هستند: شما یک تابع (کنترلکننده رویداد) ارائه میدهید که نه بلافاصله، بلکه هر زمان که رویداد اتفاق میافتد، فراخوانی میشود. اگر "رویداد" به صورت "عملیات ناهمزمان تکمیل شده است" باشد، میتوان از آن رویداد برای اطلاعرسانی به فراخواننده در مورد نتیجه فراخوانی تابع ناهمزمان استفاده کرد.
برخی از APIهای ناهمزمان اولیه از رویدادها دقیقاً به همین روش استفاده میکردند. API XMLHttpRequest به شما امکان میدهد درخواستهای HTTP را با استفاده از جاوا اسکریپت به یک سرور راه دور ارسال کنید. از آنجایی که این کار میتواند مدت زیادی طول بکشد، یک API ناهمزمان است و با اتصال شنوندههای رویداد به شیء XMLHttpRequest، از پیشرفت و تکمیل نهایی یک درخواست مطلع میشوید.
مثال زیر این را در عمل نشان میدهد. برای ارسال درخواست، "برای شروع درخواست کلیک کنید" را فشار دهید. ما یک XMLHttpRequest جدید ایجاد میکنیم و به رویداد بارگذاری آن گوش میدهیم. کنترلکننده "پایان یافت!" را ثبت میکند. پیام به همراه کد وضعیت.
پس از اضافه کردن شنونده رویداد، درخواست را ارسال میکنیم. توجه داشته باشید که پس از این، میتوانیم "درخواست XHR آغاز شده" را ثبت کنیم: یعنی برنامه ما میتواند در حین انجام درخواست به اجرا ادامه دهد و کنترلکننده رویداد ما پس از تکمیل درخواست فراخوانی میشود.
این یک کنترلکننده رویداد است، درست مانند کنترلکنندههایی که برای اقدامات کاربر مانند کلیک کاربر روی یک دکمه استفاده میشوند. با این حال، این بار، رویداد، تغییر در وضعیت یک شیء است.
یک کنترلکننده رویداد نوع خاصی از فراخوانی است. فراخوانی فقط تابعی است که به تابع دیگری ارسال میشود، با این انتظار که فراخوانی در زمان مناسب فراخوانی شود. همانطور که دیدیم، فراخوانیهای بازگشتی روش اصلی پیادهسازی توابع ناهمزمان در جاوا اسکریپت بودند.
با این حال، درک کد مبتنی بر فراخوانی میتواند دشوار شود وقتی که خود فراخوانی باید توابعی را که فراخوانی را میپذیرند فراخوانی کند. اگر نیاز به انجام عملیاتی داشته باشید که به مجموعهای از توابع ناهمزمان تجزیه میشود، این یک وضعیت رایج است. به عنوان مثال، موارد زیر را در نظر بگیرید:
در اینجا ما یک عملیات واحد داریم که به سه مرحله تقسیم شده است، که هر مرحله به مرحله آخر بستگی دارد. در مثال ما، مرحله اول ۱ را به ورودی اضافه میکند، مرحله دوم ۲ را و مرحله سوم ۳ را اضافه میکند. با شروع از ورودی ۰، نتیجه نهایی ۶ (۰ + ۱ + ۲ + ۳) است. به عنوان یک برنامه همزمان، این بسیار سرراست است. اما اگر مراحل را با استفاده از callbackها پیادهسازی کنیم، چه میشود؟
از آنجا که باید فراخوانیهای برگشتی را درون فراخوانیهای برگشتی فراخوانی کنیم، یک تابع تو در تو خواهیم داشت که خواندن و اشکالزدایی آن بسیار دشوارتر است. این حالت گاهی اوقات "جهنم فراخوانی" یا "هرم نابودی" نامیده میشود (زیرا تورفتگی آن مانند یک هرم در کنار آن است).
وقتی فراخوانیهای برگشتی را به این شکل تو در تو قرار میدهیم، مدیریت خطاها نیز میتواند بسیار دشوار شود: اغلب باید خطاها را در هر سطح از "هرم" مدیریت کنید، به جای اینکه فقط یک بار در سطح بالا مدیریت خطا داشته باشید.
به همین دلایل، اکثر APIهای ناهمزمان مدرن از فراخوانیهای برگشتی استفاده نمیکنند. در عوض، پایه و اساس برنامهنویسی ناهمزمان در جاوا اسکریپت Promise است و این موضوع مقاله بعدی است.