ru:https://highload.today/blogs/ovladej-nastoyashhej-siloj-v-node-js-razbiraem-mikro-i-makrozadachi-na-primerah/ ua:https://highload.today/uk/blogs/ovolodij-spravzhnoyu-syloyu-v-node-js-mikro-i-makro-zadachi/
logo
Back-end      05/10/2022

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

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

Senior Backend Developer | Node.js | NestJS | Express.js | SQL | NoSQL | AWS

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

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

Макро: 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()

Онлайн-курс "Комунікаційний менеджер" від Skvot.
Ви отримаєте скіли комунікації, сформуєте CV та розробите власну one page strategy. Для своєї карʼєри та успішного масштабування бренду.
Програма курсу і реєстрація

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

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. У всіх інших випадках краще використовувати макрозадачі, тому що їх поведінка більш передбачувана.

Онлайн курс UI/UX Design Pro від Ithillel.
Навчіться проєктувати інтерфейси з урахуванням поведінки користувачів, розв'язувати їх проблеми через Customer Journey Mapping, створювати дизайн-системи і проводити дослідження юзабіліті, включаючи проєктування мобільних додатків для Android та iOS і розробку UX/UI на основі даних!
Дізнатися більше
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.

Онлайн-курс "QA Automation" від robot_dreams.
Це 70% практики, 30% теорії та проєкт у портфоліо.Навчіться запускати перевірку сотень опцій одночасно, натиснувши лише одну кнопку.
Детальніше про курс

Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.

Топ-5 найпопулярніших блогерів березня

PHP Developer в ScrumLaunch
Всего просмотровВсього переглядів
2434
#1
Всего просмотровВсього переглядів
2434
Founder at Shallwe, Python Software Engineer (Django/React)
Всего просмотровВсього переглядів
113
#2
Всего просмотровВсього переглядів
113
Career Consultant в GoIT
Всего просмотровВсього переглядів
95
#3
Всего просмотровВсього переглядів
95
CEO & Founder в Trustee
Всего просмотровВсього переглядів
94
#4
Всего просмотровВсього переглядів
94
Рейтинг блогерів

Найбільш обговорювані статті

Топ текстів

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: