ایونت لوپ Event Loop از مفاهیم هسته ای ند جی اس Nodejs

5 اردیبهشت 1400

ایونت لوپ یکی از مهمترین جنبه های ند جی اس است که باید به خوبی درک شود. این مقاله از دایکومنتیشن سایت رسمی ند جی اس ترجمه شده است.

چرا این موضوع اینقدر مهم است؟ زیرا توضیح می دهد که چگونه Node.js می تواند asynchronous (ناهمزمان) باشد و non-blocking I/O (ورودی و خروجی غیر مسدود کننده) داشته باشد.

کد های جاوااسکریپت در Node.js بر روی یک موضوع اجرا می شود. هر بار فقط یک اتفاق می افتد.

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

شما فقط به نحوه نوشتن کد خود توجه کنید! از هر چیزی که می تواند موضوع را مسدود کند مانند تماس های همزمان شبکه (synchronous network calls) یا حلقه های بی نهایت (infinite loops) خودداری کنید.

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

محیط بطور همزمان چندین ایونت لوپ را مدیریت می کند. مثلا یک event loop برای web workers ایجاد میشود و یک event loop برای درخواست های API !

برای پیشگیری از بلاک شدن یک رویداد همواره در حین کدنویسی در نظر داشته باشید که کدهایتان در یک حلقه رویداد اجرا شود.

بلاک کردن ایونت لوپ Blocking Event Loop

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

تقریباً تمام ابتدای I / O موجود در JavaScript بلاک کننده نیستند. مانند Network requests و filesystem operations. بلاک کردن یک استثنا است. به همین دلیل جاوا اسکریپت بسیار مبتنی بر callbacks است. اخیراً بر اساس promise و async/await است.

کال استک call stack

کال استک، یک استک LIFO است. مخفف Last In First Out است.

ایونت لوپ بطور مداوم کال استک را چک میکند تا فانکشن هایی که باید ران شوند را ران کند.

در حالی که این کار را انجام می دهد، فانکشن هایی را که پیدا می کند، به کال استک اضافه می کند و هر کدام را به ترتیب اجرا می کند.

احتمالا با کال استک ارور اشنا هستید! آن را در کنسول بروزر یا دیباگر دیده اید؟ بروزر در کال استک، نام فانکشن ها را جستجو میکند تا به شما بگوید در هر لحظه کدام فانکشن در کال استک کنونی در حال اجراست.

با توجه به این تصویر بگویید کدام فانکشن در این کال استک در حال اجرا بوده که در حال اجرایش به ارور برخورده است؟

یک توضیح ساده از ایونت لوپ

بیایید روی یک مثال کار کنیم:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  bar()
  baz()
}

foo()

وقتی کدهای بالا اجرا شود، ابتدا فانکشن foo اجرا میشود. داخل foo ابتدا bar و سپس baz اجرا خواهد شد.

کال استک اینگونه خواهد بود:

ایونت لوپ در هر iteration به داخل کال استک نگاه میکند، اگر چیزی باشد اجرا میکند.

ایونت لوپ به کار خود ادامه میدهد تا زمانیکه کال استک از تمام itreration ها خالی شود.

صف بندی کردن فانکشن ها برای اجرا شدن در نوبت خودشان

در مثال بالا چیز خاصی وجود ندارد. جاوااسکریپت هر چیز را پیدا میکند و به نوبت اجرا میکند.

بیایید ببینیم چگونه میتوان اجرا شدن یک فانکشن را به تعویق انداخت. مثلا تعویق انداختن به زمانی که کال استک خالی شده است و دیگر هیچ فانکشنی در کال استک برای اجرا شدن وجود ندارد.

کاربرد setTimeout(() => {}, 0) برای فراخوانی یک تابع است. اما این تابع فراخوانی شده را در زمانی اجرا میکند که هیچ فانکشن دیگری برای اجرا شدن وجود نداشته باشد.

به این مثال نگاهی بیندازید:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  baz()
}

foo()

این کد بصورت شگفت آوری این خروجی را نشان میدهد:

foo
baz
bar

وقتی کدها اجرا میشوند ابتدا foo اجرا میشود، سپس داخل foo تابع baz اجرا میشود. زیرا setTimeout اجرای bar را به آخرین اجرا انتقال داده است. مقدار صفر نشان میدهد که bar با فاصله زمانی صفر ثانیه پس از اجرای تمام فانکشن ها باید اجرا شود.

در این مورد کال استک اینگونه میشود:

این ترتیب اجرای تمام iteration های ما در این برنامه است:

چرا این اتفاق افتاد؟

صف پیام Message Queue

وقتی setTimeout اجرا میشود بروزر یا ند جی اس، timer را فعال میکند. وقتی timer منقضی شد، (در این مثال که مقدار صفر را قرار دادیم) با فاصله زمانی صفر ثانیه، callback function در message queue (صف پیام) قرار داده میشود. منظور از callback function همان bar است که داخل setTimeout قرار داده شده است.

ایونت های دیگری هم به صف پیام منتقل میشوند. مثلا رویداد های موس و کیبورد مثل onClick ! مثلا تا قبل از اینکه پاسخ fetch به کدها برسند! یا ایونت های DOM مانند onLoad !

حلقه اولویت را به کال استک میدهد. حلقه در ابتدای کار خودش سراغ کال استک میرود و هرچه آنجا بیابد را پردازش میکند. وقتی پردازش های کال استک تمام شد سراغ پردازش های داخل message queue میرود.

لازم نیست منتظر توابعی مانند setTimeout یا fetch یا موارد دیگر باشیم. توسط مرورگر ارائه می شوند و در رشته خاص خود زندگی می کنند. به عنوان مثال ، اگر زمان setTimeout را روی 2 ثانیه تنظیم کنید ، لازم نیست 2 ثانیه صبر کنید – انتظار در جای دیگری اتفاق می افتد.

صف کارها در اکما اسکریپت شش (ES6 Job Queue)

ECMAScript 2015 مفهوم job queue را که توسط Promises استفاده می شود (در ES6 / ES2015 نیز معرفی شده است) معرفی کرد. این روشی برای اجرای نتیجه async function در اسرع وقت است، به جای اینکه در انتهای کال استک قرار گیرد.

پرامیس هایی که قبل از پایان فانکشن کنونی resolve می شوند، بعد از فانکشن کنونی اجرا می شوند.

در شهربازی یک ترن هوایی را در نظر بگیرید: message queue شما را در پشت تمام افراد در صف قرار می دهد، در حالی که job queue بلیط دومی است که به شما امکان می دهد بعد از اتمام اولین بار برای بار دوم سوار ترن شوید.

به عنوان مثال:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  new Promise((resolve, reject) =>
    resolve('should be right after baz, before bar')
  ).then(resolve => console.log(resolve))
  baz()
}

foo()

نتیجه کدهای بالا این میشود:

foo
baz
should be right after baz, before bar
bar

تفاوت زیادی بین پرامیس (async/await) و فانکشن های قدیمی و ساده ی asynchronous که دارای setTimeout هستند، وجود دارد. همچنین تفاوت زیادی بین پرامیس و دیگر platform APIs وجود دارد.

این کال استک مربوط به مثال بالاست:

اگر میخواهید مقاله مرتبط دیگری در این باره بخوانید این مقاله نیز از دایکومنتیشن سایت رسمی ند جی اس ترجمه شده است.