Привіт усім! В цій статті я розповім, як оперувати виконанням коду на більш нижчому рівні. Якщо ви розумієте, що у вашому коді є макро- і мікрозадачі, то ви можете більше передбачити поведінку коду і послідовність виконання певних функцій та методів, що значно полегшує роботу в оптимізації вашого додатку.
Макро: 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
Розберемо отриманий результат:
console.log() — завжди виконуються першими, тому що це теж I/O-операція і вона — завжди перша після ініціалізації коду. Це відбувається через те, що таймери виконуються після того, як будуть назначені — їх поведінку контролює poll-фаза, а до next tick queue черга ще не дійшла.process.nextTick() — виконується другим, тому що він спрацьовує в next tick queue.queueMicrotask() є альтернативою process.nextTick() і виконується в тій самій черзі, де і Promise, тому виконується завжди після process.nextTick(). Але з Promise виконуються на рівних правах, тобто залежно лише від послідовності в коді.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.
Отже, ми розглянули детально мікро- і макрозадачі. І тепер кожен, прочитавши цю статтю і протестуючи код в прикладах, зможе чітко відповісти на питання стосовно послідовності виконання різних задач.
За що вам дякую і бажаю всім продуктивного кодування 😉
Ідея «соціальних фінансів» у криптосвіті витає вже кілька років, але все ніяк не може набути…
За останні десять років криптоіндустрія пройшла шлях від експериментальної ніші до одного з ключових сегментів…
Щосекундно збільшується обсяг інформації в мережі. Бізнес збирає дорогоцінні байти даних, структурує їх, аналізує і…
Штучний інтелект (ШІ) вже не просто модне слово, а рушійна сила, що змінює саму суть…
Алгоритм консенсусу – це серце будь-якого блокчейна. Саме він визначає, хто і як записує нові…
Зайшов на сторінку, а там — спінери, skeleton і порожнеча? Це не баг, це —…