Привет всем! В этой статье я расскажу, как оперировать выполнением кода на более низком уровне. Если вы понимаете, что в вашем коде есть макро- и микрозадачи, то вы можете больше предсказать поведение кода, последовательность выполнения определенных функций и методов, что значительно упрощает работу в оптимизации вашего приложения.
Макро: 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
.
Итак, мы подробно рассмотрели микро- и макрозадачи. И теперь каждый, прочитав статью и протестируя код в примерах, сможет четко ответить на вопросы относительно последовательности выполнения различных задач.
За что вам спасибо и желаю всем продуктивного кодинга 😉
В благословенные офисные времена, когда не было большой войны и коронавируса, люди гораздо больше общались…
Вот две истории из собственного опыта, с тех пор, когда только начинал делать свою карьеру…
«Ты же программист». За свою жизнь я много раз слышал эту фразу. От всех. Кто…
Отличные новости! Если вы пропустили, GitHub Copilot — это уже не отдельный продукт, а набор…
Несколько месяцев назад мы с командой Promodo (агентство инвестировало в продукт более $100 000) запустили…
Пару дней назад прочитал сообщение о том, что хорошие курсы могут стать альтернативой классическому образованию.…