Що таке цикл подій, як він працює і чому про нього всі завжди питають на співбесідах? Певний час я не міг чітко відповісти на це питання, а вже потім, коли з часом набрався досвіду і сам почав наймати людей, то стало зрозуміло, що це реально велика прогалина у більшості JavaScript-розробників.
1Що таке Event Loop в Node.js
Event Loop — це цикл, за допомогою якого Node.js має змогу виконувати неблокуючі операції I/O — input/output.
І ще є дуже важливим, що Event Loop — це тільки «серце» великого механізму відомої бібліотеки libuv. Все, що потрібно знати про libuv, я описав у своїй статті «Фундамент для JavaScript-розробника: як відповісти, що таке libuv на співбесіді з Node.js».
Тож зараз ми розглянемо, що всередині Event Loop:
Фази — те, про що далеко не всі знають або говорять, але знання фаз і є фундаментом для розуміння послідовності виконання коду, написаного на JavaScript.
2 Розкажіть про фази
- Timers: фаза, в якій виконуються колбеки, заплановані
setTimeout()
іsetInterval()
. - Pending callbacks: виконує I/O-колбеки, які були відкладені до наступної ітерації циклу.
- Idle, prepare: використовувати тільки внутрішньо.
- Poll: отримання нових подій I/O; виконувати колбеки, пов’язані з I/O (майже всі, за винятком колбеків, які виконуються в фазі
close callbacks
, запланованих таймерами таsetImmediate()
); при необхідності node може тут блокуватися. - Check: тут викликаються
setImmediate() callbacks
. - Сlose callbacks: закриває колбеки такі як
socket/http/eventEmitter/.on(‘close', () =>)
.
З повною інформацією про фази можна ознайомитися тут.
3Що таке мікро- та макрозадачі
Тож після запитань «що таке Event Loop» і «що ви знаєте про фази» запитують, чи знаєте ви, що таке мікрозадачі та макрозадачі, у відповідь на це запитання у мене також є стаття.
А зараз ми розберемо наступне:
- різницю між
setTimeout()
isetImmediate()
; - різницю між
process.nextTick()
іsetImmediate()
; - які труднощі можливо вирішити за допомогою
process.nextTick()
.
4У чому різниця між setTimeout() i setImmediate()
setTimeout()
— колбек, який ми передаємо в таймер, виконується після певного пройденого часу, переданого другим аргументом в setTimeout()
або при відсутності вказаного часу, за замовчуванням через 4 мс.
setImmediate()
виконується після поточної poll-фази:
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('Timeout'); }, 0); setImmediate(() => { console.log('Immediate'); }); });
Результат:
Immediate Timeout
Але що є важливим, що залежно від контексту, в якому знаходяться функції, впливає на те, чій колбек буде виконаний першим.
Коли ми використовуємо обидва таймера в логіці, яка працює з I/O, як в прикладі зверху, то setImmediate()
буде завжди першим при умові, що передані в них колбеки будуть схожі за логікою або однакові.
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
В іншому випадку таймери будуть виконуватись непередбачувано. Нижче результат двух запусків таймерів:
Immediate Timeout
Timeout Immediate
5У чому різниця між process.nextTick() і setImmediate()
В цій ситуації завжди першим виконується process.nextTick()
, він виконується при наступному тіку (tick) ядра вашого комп’ютера, а це 100-1000 тіків за секунду — і в цьому його небезпека.
Якщо ви написали рекурсивну функцію і там є process.nextTick()
, то поточний цикл Event Loop так може і не завершитись, про це є застереження в офіційній документації.
6Які труднощі можливо вирішити за допомогою process.nextTick()
Є такі ситуації, коли нам треба виконати нашу функцію з мінімальною затримкою лише після того, як код буде ініціалізовано, але ще жодна I/O-операція ще не буде виконана, і тут process.nextTick()
допоможе нам з легкістю:
const EventEmitter = require('events'); class MyEmitter extends EventEmitter { constructor() { super(); process.nextTick(() => { this.emit('event'); }); } } const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });
Цей приклад коду з офіційної документації, і дуже простий, але він зрозуміло і коротко показує нам яку важливу проблему ми можемо вирішити:
const EventEmitter = require('events'); class MyEmitter extends EventEmitter { constructor() { super(); this.emit('event'); } } const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });
Якщо виключити process.nextTick()
з нашого прикладу, то подія event
ніколи не спрацює, тому що виклик події відбувся ще у конструкторі класу MyEmitter
, а реєстрація події — пізніше.
Висновок
В мене з досвіду були кандидати з інших країн, які на запитання «що таке Event Loop» відповідали так: «Навіщо ви ставите мені таке елементарне запитання, я людина з досвідом 5+ років, і клієнт за такі знання не платить, він платить за фічі».
Звичайно, всі платять за функціонал, тільки ось цікаво, скільки буде витрачено часу і яка буде якість коду, якщо ти не знаєш, як все працює та яка сила є в наших руках.
Знання таких речей як фази і робота з ними допомагає нам розуміти більш проблемні місця в коді, писати код більш осмислено та вирішувати більш нетривіальні задачі без «милиць», що впливає на надійність і впевненість в роботі нашої системи.
Дякую вам за увагу і продуктивного кодування 😉
If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.
Favbet Tech – це ІТ-компанія зі 100% українською ДНК, що створює досконалі сервіси для iGaming і Betting з використанням передових технологій та надає доступ до них. Favbet Tech розробляє інноваційне програмне забезпечення через складну багатокомпонентну платформу, яка здатна витримувати величезні навантаження та створювати унікальний досвід для гравців.
Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: