حلقه رویداد (Event Loop)‍ در NodeJS

نود جی‌اس (NodeJS) یک محیط اجرای مبتنی‌بر‌رویداد ناهمگام جاوا اسکریپت است که برای ساخت برنامه‌های مقیاس‌پذیر تحت شبکه طراحی شده است. منظور از ناهمگام آن دسته از توابع در جاوا اسکریپت است که در پس‌زمینه و بدون مسدود‌کردن سایر درخواست‌ها پردازش می‌شوند.این نوشتار نحوه عملکرد NodeJS و همچنین مدیریت توابع یا درخواست‌هایی که به صورت همگام یا ناهمگام به سمت سرور ارسال شده‌اند را بررسی می‌کند.حلقه رویداد (Event Loop)‍ چیست؟رسیدگی به درخواست‌ها در محیط NodeJS، با استفاده از Event Loop صورت می‌پذیرد. حلقه رویداد یا Event Loop یک شنونده رویداد یا event-listener است که در محیط NodeJS ایفای نقش می‌کند و همواره آماده‌ی شنود، پردازش و برونداد(ایجاد) یک رویداد است. رویداد می‌تواند هر چیزی باشد. کلیک ماوس، فشار دکمه‌ای از کی‌بورد و وقفه، نمونه‌هایی از رویداد هستند.برنامه‌نویسی همگام و ناهمگامدر برنامه‌نویسی همگام، کد به همان ترتیبی که مشخص شده‌ است اجرا می‌شود. در یک برنامه همگام، زمانی که تابعی فراخوانی می‌شود، تنها در صورت اتمام اجرای این تابع، خط بعدی کد اجرا می‌شود.بیایید این مفهوم از برنامه نویسی را با یک مثال درک کنیم:const listItems = function(items) { items.forEach(function(item) { console.log(item) })}const items = ["Buy milk", "Buy coffee"]listItems(items)خروجی‌های برنامه فوق به شرح زیر هستند:"Buy milk""Buy coffee"در این مثال، زمانی که تابع listItems(items) فراخوانی می‌شود، از طریق حلقه آیتم‌های آرایه را پیمایش می‌کند، و در هر پیمایش از حلقه تابع console.log(item) فراخوانی می‌شود. تابع console.log(item) در اولین پیمایش حلقه، اولین مؤلفه از آرایه را به عنوان آرگومان ورودی می‌پذیرد و به موجب آن "Buy milk" را چاپ می‌کند. سپس در پیمایش دوم برای باری دیگر console.log(item) اجرا می شود و این بار مؤلفه دوم آرایه را به عنوان آرگومان ورودی می‌پذیرد و به موجب آن "Buy coffee" را چاپ می‌کند. بنابراین می‌توان گفت که تابع به همان ترتیبی که تعیین گردیده، اجرا شده است.از سوی دیگر برنامه‌نویسی ناهمگام کدهایی را در برمی‌گیرد که به ترتیب اجرا نمی‌شوند. این توابع نه بر اساس ترتیبی که در یک برنامه تعیین گردیده، بلکه تنها زمانی اجرا می‌گردند که شرایط خاصی برآورده شوند.به عنوان مثال، تابع ()setTimeOut عملیات مشخصی را پس از اتمام زمانی معین ( که بر حسب میلی‌ثانیه از پیش تعریف شده است) انجام می‌دهد.setTimeOut(function(){ return( console.log("Hello World!") )}, 3000)این توابع (ناهمگام)‌ خط به خط اجرا نمی‌شوند، و تنها در صورت برقراری شرایطی خاص اجرای آن‌ها به جریان درمی‌آید. در مثال فوق که نمونه‌ای از رویکرد استفاده از توابع ناهمگام است، پس از اجرای تمامی توابع همگام، تابع ناهمگام پس از ۳ ثانیه (۳۰۰۰ میلی‌ثانیه) به طور خودکار اجرا می‌شود.توجه: توابع ناهمگام فقط پس از اجرای همه توابع همگام اجرا می شوند. تا آن زمان، آنها در پس‌زمینه پردازش خواهند شد.سوالی که در اینجا به ذهن خطور می‌کند این است که NodeJS چگونه توابع ناهمگام را در پس‌زمینه مدیریت کرده و قبل از آن‌ها (توابع ناهمگام) تمامی توابع همگام را اجرا می‌کند؟ پاسخ به این سوال را می توان به راحتی با Event Loop در NodeJS توضیح داد.حلقه رویداد یا Event Loop چگونه کار می‌کند؟برای رسیدن به پاسخ این سوال، در ابتدا بررسی می‌کنیم که چگونه Event Loopها در NodeJS یک برنامه همگام ساده را بر مبنای نمودار حلقه رویداد Nodejs اجرا می‌کنند. پس از آن به این مساله خواهیم پرداخت که Node به چه صورت برنامه را خط به خط اجرا می‌کند.با مطالعه دقیق این بخش، موضوع برایتان روشن خواهد شد:در گوشه بالا و سمت چپ تصویر، یک فایل Node وجود دارد که قرار است اجرا شود. در پایین و سمت چپ تصویر نیر یک ترمینال خروجی برای برنامه درنظر گرفته شده است. افزون بر این‌ها این ساختار شامل Call stack ،Node API و Callback queue است. همه این‌ها در کنار هم، محیط NodeJS را تشکیل می‌دهند.برای برنامه‌نویسی همگام، فقط باید روی Call stack تمرکز کنید. در واقع Call stack تنها بخشی از محیط NodeJS است که در برنامه‌نویسی همگام ( از میان سه بخش Call stack ،Node API و Callback queue) به کار گرفته می‌شود.بخش Call stack یک ساختمان داده‌ است که شما از آن برای دنبال‌کردن اجرای تمامی توابع اجرایی داخل برنامه استفاده می‌کنید. این ساختمان داده بر اساس تعریف استک، تنها یک انتهای باز برای افزودن یا حذف موارد دارد.هنگامی که برنامه شروع به اجرا می‌کند، ابتدا درون یک تابع ()main ناشناس قرار می‌گیرد. این فرایند به طور خودکار توسط NodeJS ایجاد میٰ‌شود. بنابراین ()main ابتدا به پشته callback پوش می‌شود.سپس متغیرهای a و b ایجاد شده و مجموع آنها در متغیر sum ذخیره می‌شود. تمامی این مقادیر در حافظه ذخیره می‌شوند. حال، ()console.log تابعی است که فراخوانی شده و در Call stack پوش می‌شود. این تابع اجرا شده و در نتیجه این اجرا می‌توان خروجی مورد انتظار را در صفحه ترمینال مشاهده کرد.پس از اجرای ()console.log، این تابع از Call stack حذف می‌شود. سپس نیز از Call stack حذف می‌شود زیرا هیچ تابعی در داخل تابع ()main برای فراخوانی باقی نمانده است. این رویه چگونگی اجرای یک برنامه همگام را توصیف می‌کند.اکنون، به چگونگی اجرای توابع یا برنامه‌های ناهمگام در NodeJS می‌پردازیم. پردازش یک تابع ناهمگام نیازمند استفاده توأم از Call Stack و Node APIها و همچنین Callback Queue است.بیایید چگونگی اجرای توابع یا برنامه‌های ناهمگام در NodeJS را با در نظرگرفتن مثال پیش‌رو بررسی کنیم:طبق معمول، وقتی برنامه شروع به اجرا می کند، ابتدا تابع ()main به Call Stack اضافه می‌شود. سپس تابع console.log("Start") فراخوانی شده و به Call Stack اضافه می‌گردد. پس از پردازش این تابع، خروجی "Start" در ترمینال به نمایش درآمده و سپس تابع console.log("Start") از Call Stack حذف می‌شود.تابع بعدی setTimeOut (با زمان دِرَنگ ۰) است که به Call Stack اضافه می‌شود. از آنجایی که این تابع ناهمگام است، در Call Stack پردازش نمی‌شود. بنابراین از Call Stack به Node APIها اضافه می‌شود، جایی که یک رویداد ثبت می‌شود و یک تابع callback تنظیم می‌شود تا در پس‌زمینه پردازش شود.تابع بعدی setTimeOut (با زمان دِرَنگ ۲۰۰۰، یا ۲ ثانیه) است که همچون تابع setTimeOut قبلی (از آن‌رو که تابعی ناهمگام است) از Call Stack به Node APIها اضافه می‌شود و در آنجا بر اساس این تابع یک رویداد ثبت می‌شود و یک تابع callback تنظیم می‌شود تا پس از ۲۰۰۰ میلی ثانیه در پس‌زمینه پردازش شود. تا این مرحله سایر توابع می‌توانند اجرا شوند.این رفتار غیر مسدود کننده نامیده می‌شود که در آن ابتدا تمام توابع همگام پردازش و اجرا می شوند و توابع ناهمگام در پس زمینه در حالی که منتظر نوبت اجرای خود هستند پردازش می‌شوند.در ادامه، تابع console.log("End") در Call Stack فراخوانی می‌شود و در همین محل (از آن‌رو که تابعی همگام است) پردازش می‌شود. در این مرحله می‌توانید خروجی تابع console.log("End") را در ترمینال ببینید. تا بدین لحظه، تمامی توابع همگام پردازش شده‌اند و تابع ()main از Call Stack حذف می‌شود.در این حال و در پس‌زمینه، تمام توابع ناهمگام پردازش می‌شوند وتوابع callback آن‌ها در Callback Queue ذخیره می‌شوند. تابع ناهنگامی که در ابتدا پردازش شد، در ابتدای صف به منظور اجرا در Call Stack قرار می‌گیرد.توجه: توابع ناهمگام نمی‌توانند در داخل Call Stack تا زمانی که خالی نشود اجرا شوند. این بدان معناست که تنها پس از حذف ()main از Call Stack، توابع ناهمزمان می‌توانند اجرا شوند. سپس، هر یک از توابع callback با استفاده از Event Loop به Call Stack پوش شده و در نهایت اجرا می‌شوند. هر یک از توابع callback مقدار موردنظر را با استفاده از تابع ()console.log در هر بار فراخوانی چاپ می‌کنند.در نهایت، توابع callback پس از اجرا از Call Stack حذف شده و Call Stack خالی می‌گردد.رویکردی که به آن در قالب یک مثال ساده پرداختیم، نشان میٰ‌دهد که چگونه NodeJS توابع همگام و ناهمگام را در داخل محیط خود اجرا می کند و همچنین چگونه Event Loop فراخوانی توابع ناهمگام را میسر می‌سازد.جمع‌بندیدر این نوشتار، عملکرد داخلی NodeJS را از نظر گذراندیم و همچنین نحوه اجرای برنامه‌های ناهمگام را مشاهده کردیم. اکنون براحتی درک می‌کنیم که چرا تابع setTimeOut (با زمان دِرَنگ ۲۰۰۰، یا ۲ ثانیه) اجرای بقیه برنامه‌ها را مسدود نمی‌کند. همچنین دلیل اینکه چرا تابع setTimeOut (با زمان دِرَنگ ۰) در نهایت مقدار مورد نظر خود را پس از چاپ "End"، چاپ می‌کند اکنون برای ما روشن است.همین! امیدوارم از خواندن این نوشتار لذت برده و چیز جدیدی را یاد گرفته باشید. اگر حس میٰ‌کنید این نوشتار می‌تواند برای شخص دیگری نیز مفید واقع شود، آن را به اشتراک بگذارید.منبعترجمه ای از Event Loops in NodeJS – Beginner s Guide to Synchronous and Asynchronous Code