Рубріки: Back-end

Опануй справжню силу в Node.js: розбираємо мікро- та макрозадачі на прикладах

Владислав Хирса

Привіт усім! В цій статті я розповім, як оперувати виконанням коду на більш нижчому рівні. Якщо ви розумієте, що у вашому коді є макро- і мікрозадачі, то ви можете більше передбачити поведінку коду і послідовність виконання певних функцій та методів, що значно полегшує роботу в оптимізації вашого додатку.

Що таке макро- і мікрозадачі

Макро: setTimeout, setImmediate, setInterval, I/O, UI rendering.

Мікро: Promise, process.nextTick, queueMicrotask, і на фронті ми маємо наглядача за DOM елементами MutationObserver.

Різниця між ними в тому, що мікрозадачі мають пріоритет перед макрозадачами.

Вони виконуються в першу чергу, лише після того, як виконуються всі мікрозадачі. Event loop переходить до черги макрозадач — і потім знову після макрозадачі виконуються всі мікро-. І так по колу, поки Event queue не стане порожнім.

Як обробляються задачі?

У нас є черга подій Event queue, в якій знаходяться всі наші події з їхніми обробниками в черзі. Event loop обробляє всі події по принципу FIFO (first in first out) — тобто остання подія буде оброблена в останню чергу — все як за етикетом 🙂

Проте у нас є можливість піти поза чергою, використати, так би мовити, VIP-перепустку і обійти зареєстровані події 😉 Ми можемо це зробити завдяки декільком методам:

1. process.nextTick()

2. queueMicrotask()

3. setImmediate()

Зараз пропоную вам детально розглянути код, який наочно покаже, яка функція виконується і в якій послідовності, не дивлячись на те, де вона розташована і скільки часу потрібно для її виконання:

const fibonacci = n => {
  if(n <= 1){
    return n;
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}
const loging = (...args) => {
  const [ colorKey, text, fibNumber ] = args;
  const colors = {
    y:'x1b[33m%sx1b[0m',
    b:'x1b[34m%sx1b[0m',
    w:'x1b[37m%sx1b[0m',
  };
  const color = colors[colorKey] || colors['w'];
  console.log(color, text + ' ' + fibonacci(fibNumber));
}
const task = async(a) => {
  const task2 = (t) =>Promise.resolve(t());
  return await task2(fibonacci.bind(null, a));
}
const taskContainer = () => {
  console.log('x1b[32m%sx1b[0m', '--- START taskContainer ---');
  setImmediate(() =>loging('y', '2 -- setImmediate', 15)); // not regular execution
  setTimeout(() =>loging('y', '2 -- setTimeout', 15)); // not regular execution
  queueMicrotask(() =>loging('y', '2 -- queueMicrotask', 20));
  process.nextTick(() =>loging('y', '2 -- nextTick', 35));
  Promise.resolve().then(_=>loging('y', '2 -- Promise', 12));
 
  console.log('x1b[31m%sx1b[0m', '--- END taskContainer ---');
};
setImmediate(() => loging('b', '1 -- setImmediate', 15)); // not regular execution
setTimeout(() => loging('b', '1 -- setTimeout', 15)); // not regular execution
task(20).then(res => console.log('Nested task result', res));
Promise.resolve().then(_ => loging('b', '1 -- Promise', 32));
queueMicrotask(() => loging('b', '1 -- queueMicrotask', 20));
process.nextTick(() => loging('b', '1 -- nextTick', 35));
console.log('x1b[34m%sx1b[0m', `1 -- log ${fibonacci(10)}`);
taskContainer();

Результат:

Console output:

1 -- log 55

--- START taskContainer ---

--- END taskContainer ---

1 -- nextTick 9227465

2 -- nextTick 9227465

1 -- Promise 2178309

1 -- queueMicrotask 6765

2 -- queueMicrotask 6765

2 -- Promise 144

Nested task result 6765

1 -- setTimeout 610

2 -- setTimeout 610

1 -- setImmediate 610

2 -- setImmediate 610

Розберемо отриманий результат:

  1. console.log() — завжди виконуються першими, тому що це теж I/O-операція і вона — завжди перша після ініціалізації коду. Це відбувається через те, що таймери виконуються після того, як будуть назначені — їх поведінку контролює poll-фаза, а до next tick queue черга ще не дійшла.
  2. process.nextTick() — виконується другим, тому що він спрацьовує в next tick queue.
  3. queueMicrotask() є альтернативою process.nextTick() і виконується в тій самій черзі, де і Promise, тому виконується завжди після process.nextTick(). Але з Promise виконуються на рівних правах, тобто залежно лише від послідовності в коді.
  4. setTimeout/setImmediate() — це таймери, вони є макрозадачами, завжди виконуються після мінімальної затримки, якщо вона не вказана або, як ми знаємо, після всіх мікрозадач.

Як ми можемо використати отримані знання

Мікрозадачі ми використовуємо для того, щоб виконати асинхронну роботу коду. Це є надважливим, коли ми, наприклад, хочемо виконати функцію після ініціалізації всього коду даного файла, але до того, як весь код починає виконуватись.

Виключенням можуть бути деякі I/O-операції, вони виконуються синхроно одразу після ініціалізації, але перед next tick queue. У всіх інших випадках краще використовувати макрозадачі, тому що їх поведінка більш передбачувана.

const importantObject = {
  _name:'Vladyslav'
}
process.nextTick(() => {
  console.log('My name is ', importantObject.getName());
});
importantObject.getName = function () {
  return this._name
}

Результат:

Console output: `My name is Vladyslav`

На прикладі цього коду ми можемо побачити, як ми відклали виконання методу importantObject.getName() і виведення його результату в консоль завдяки process.nextTick(). Таким чином ми зачекали його ініціалізацію.

const importantObject = {
  _name:'Vladyslav'
}
console.log('My name is ', importantObject.getName());
importantObject.getName = function () {
  return this._name
}

Результат:

Console output: `TypeError: importantObject.getName is not a function`

Без process.nextTick() ми отримаємо помилку, бо викличемо той метод якого ще не матимемо в importantObject.


Отже, ми розглянули детально мікро- і макрозадачі. І тепер кожен, прочитавши цю статтю і протестуючи код в прикладах, зможе чітко відповісти на питання стосовно послідовності виконання різних задач.

За що вам дякую і бажаю всім продуктивного кодування 😉

Якщо ви знайшли помилку, будь ласка, виділіть фрагмент тексту та натисніть Ctrl+Enter.

Останні статті

ChatGPT, моторошна долина та трохи Фройда

Днями я завзято нила про щось ChatGPT (експериментую між сеансами з живим терапевтом). І от…

17.04.2025

Я прийшла за покупками, а не крутити колесо

«Крутіть колесо, щоб отримати знижку до 50%!» «Натисніть тут, щоб відкрити таємничу пропозицію!» «Зареєструйтесь зараз,…

16.04.2025

Майже навайбкодив десктопний монітор CI пайплайнів

Дуже хочеться робити якісь десктопні апки. Сумую за часами коли всі програми були offline-first, і…

15.04.2025

Як працюють транзакційні комісії в мережах Bitcoin і Ethereum

Надсилаючи криптовалюту, багато новачків ставлять запитання: як працюють комісії та чому вони відрізняються в різних…

14.04.2025

Обережно, тепер вас можуть обдурити на співбесіді з роботодавцем

Нова афера набирає обертів — ось детальний розбір того, як фальшиві потенційні роботодавці намагаються вкрасти…

11.04.2025

Цілі застосунки в соцмережі? На останньому ETHKyiv Impulse довели, що це можливо

Соцмережа з можливістю вбудовувати повноцінні додатки прямо в пости — звучить як фантастика, але Farcaster…

10.04.2025