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

Сейчас предлагаю вам подробно рассмотреть код: какая функция выполняется в какой последовательности, несмотря на то, где она расположена и сколько времени требуется для ее выполнения:

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

Онлайн-курс "Digital Marketing" від Laba.
Розширте пул навичок у роботі з аудиторією.Навчіться запускати рекламні кампанії без зайвих витрат бюджету з сучасними інструментами діджитал-маркетингу, включаючи AI.
Детальніше про курс
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.


Итак, мы подробно рассмотрели микро- и макрозадачи. И теперь каждый, прочитав статью и протестируя код в примерах, сможет четко ответить на вопросы относительно последовательности выполнения различных задач.

За что вам спасибо и желаю всем продуктивного кодинга 😉

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Курс QA від Mate academy.
Найпростіший шлях розпочати кар'єру в ІТ та ще й з гарантованим працевлаштуванням.
Інформація про курс

Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Топ-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
Рейтинг блогеров

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

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

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