Різниця між мікрозадачею та макроскладом у контексті циклу події


140

Я щойно закінчив читати специфікацію Promises / A + і натрапив на умови microtask та macrotask: див. Http://promisesaplus.com/#notes

Я ніколи раніше не чув про ці терміни, і зараз мені цікаво, яка може бути різниця?

Я вже намагався знайти деяку інформацію в Інтернеті, але все, що я знайшов, - це публікація з архіву w3.org (яка мені не пояснює різниці): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Крім того, я знайшов модуль npm під назвою "macrotask": https://www.npmjs.org/package/macrotask Знову ж таки, не з'ясовано, у чому різниця.

Все, що я знаю, - це те, що воно пов'язане з циклом подій, як це описано на https://html.spec.whatwg.org/multipage/webappapis.html#task-queue та https: //html.spec.whatwg .org / multipage / webappapis.html # виконувати-a-microtask-checkpoint

Я знаю, що теоретично я повинен мати змогу самостійно витягнути відмінності, враховуючи цю специфікацію WHATWG. Але я впевнений, що й інші можуть отримати користь від короткого пояснення, яке дав експерт.


Якщо коротко: кілька вкладених черг подій. Ви навіть можете самі реалізувати це:while (task = todo.shift()) task();
Бергі

1
Для тих, хто хоче трохи більше деталей: Секрети JavaScript Ninja, 2-е видання, РОЗДІЛ 13 Переживаючі події
Етан

Відповіді:


220

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

Які практичні наслідки цього?

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

Однак, принаймні, стосовно функції Node.js process.nextTick (яка чекає мікрозадачі ), існує вбудований захист від такого блокування за допомогою process.maxTickDepth. Це значення встановлено за замовчуванням у 1000, зменшуючи подальшу обробку мікрозадач після досягнення цієї межі, що дозволяє обробляти наступні макрозадачі )

То коли ж використовувати що?

В основному, використовуйте мікрозадачі, коли вам потрібно робити асинхронно речі синхронно (тобто, коли ви скажете виконувати це (мікро-) завдання в найближчому майбутньому ). В іншому випадку дотримуйтесь макрозадач .

Приклади

макрозадачі: setTimeout , setInterval , setImmediate , requestAnimationFrame , I / O ,
мікрозадачі, що надають користувальницький інтерфейс: process.nextTick , Обіцяння , чергаMicrotask , MutationObserver


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

2
process.maxTickDepthбуло видалено дуже давно: github.com/nodejs/node/blob/…
RidgeA

ви також можете використовувати метод queueMicrotask (), щоб додати новий мікрозадачу
ZoomAll

Дякую @ZoomAll, до цього часу не знав чергиMicrotask (). Я додав його у відповідь плюс посилання на всі речі ...
NicBright

requestAnimationFrame (rAF) не тільки генерує мікрозадачі. Взагалі, виклик rAF створює окрему чергу
ZoomAll

67

Основні поняття в специфікації :

  • У циклі подій є одна чи більше черг завдань. (Черга завдань - макрозадача черги)
  • Кожен цикл подій має чергу мікрозадач.
  • черга завдань = черга макротаків! = черга з мікрозадачею
  • завдання може бути висунуте у чергу макрозадач або чергу мікротаків
  • коли завдання висувається в чергу (мікро / макро), ми маємо на увазі підготовку роботи закінчену, тому завдання можна виконати вже зараз.

А модель процесу циклу подій така:

коли стек викликів порожній, виконайте кроки-

  1. виберіть найстаріше завдання (завдання A) у черзі завдань
  2. якщо завдання A недійсне (означає, що черги завдань порожні), перейдіть до кроку 6
  3. встановіть "поточне завдання" на "завдання А"
  4. запустити "завдання A" (означає запустити функцію зворотного виклику)
  5. встановіть значення "поточне завдання" на нуль, видаліть "завдання А"
  6. виконувати чергу мікрозадач
    • (a) .виберіть найдавніше завдання (завдання x) у черзі мікрозадач
    • (b). якщо завдання x є null (означає, що черги мікрозадач порожні), перейдіть до кроку (g)
    • (c) .встановити "поточне завдання" на "завдання х"
    • (d) .run "завдання х"
    • (e) .set: "поточне завдання, що виконується" нульове, видалити "завдання x"
    • (f) .виберіть наступне найдавніше завдання в черзі мікрозадач, перейдіть до кроку (b)
    • (g) чергова черга з мікрозадачею;
  7. перейти до кроку 1.

спрощена модель процесу така:

  1. запустіть найстаріше завдання в черзі макрозадач, а потім видаліть його.
  2. запустіть усі наявні завдання в черзі мікрозадач, а потім видаліть їх.
  3. наступний раунд: запустіть наступне завдання в черзі макрозадач (перехід крок 2)

щось запам’ятати:

  1. коли завдання (у черзі макрозадач) запущено, нові події можуть бути зареєстровані. Отже, нові завдання можуть бути створені. Далі дві нові створені завдання:
    • Завдання зворотного дзвінка obeA.then () - завдання
      • obeA вирішено / відхилено: завдання буде висунуто в чергу мікрозадач у поточному раунді циклу подій.
      • Очікує obeA: завдання буде перенесено в чергу чергових мікрозадач у майбутньому раунді циклу подій (можливо, наступного раунду)
    • setTimeout (зворотний виклик, n) - зворотний виклик є завданням, і він буде висунутий у чергу макрозадач, навіть n дорівнює 0;
  2. завдання в черзі мікрозадач буде виконуватися в поточному раунді, тоді як завдання в черзі макротаків має чекати наступного раунду циклу подій.
  3. всі ми знаємо зворотний виклик "click", "scroll", "ajax", "setTimeout" ... - це завдання, однак ми також повинні пам’ятати, що js-коди в цілому в тезі скрипту також є завданням (макрозадачею).

2
Це чудове пояснення! Дякую, що поділились!. Ще одна річ, яку слід зазначити в NodeJs , setImmediate()це макро / завдання та process.nextTick()є мікро / завдання.
LeOn - Хан Лі

6
Що з paintзавданнями браузера ? У яку категорію вони б входили?
Легенди

Я думаю, вони б вписалися в requestAnimationFrame
мікрозадачі

Ось порядок, в якому працює цикл подій v8 -> стек виклику || Мікрозадачі || Черга завдань || rAF || Дерево візуалізації || Макет || Фарба || <Рідні виклики ОС для малювання пікселів на екрані> <----- 1) DOM (нові зміни), CSSOM (нові зміни), дерево візуалізації, макет і фарба відбудуться після зворотного виклику requestAnimationFrame відповідно до таймерів циклу подій. Ось чому важливо закінчити свої операції DOM перед rAF наскільки це можливо, відпочинок може перейти на rAF. PS: виклик rAF запустить виконання макрозадачі.
Анвеш Чецька

Я не знаю, чи помиляюся, але я не згоден з цією відповіддю, мікрозадачі працюють перед макрозадачею. codepen.io/walox/pen/yLYjNRq ?
валокс

9

Я думаю, що ми не можемо обговорити цикл подій у відриві від стека, тому

JS має три "стеки":

  • стандартний стек для всіх синхронних дзвінків (одна функція викликає іншу тощо)
  • черга мікрозадач (або черга завдань чи стек мікротаків) для всіх операцій асинхронізації з більш високим пріоритетом (process.nextTick, Promises, Object.observe, MutationObserver)
  • черга макрозадач (або черги подій, черги завдань, черги макротаків) для всіх операцій асинхронізації з нижчим пріоритетом (setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, надання інтерфейсу користувача)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

І цикл подій працює таким чином:

  • виконати все знизу вгору зі стека, і ТІЛЬКИ, коли стек порожній, перевірте, що відбувається в чергах вище
  • перевірити мікро стек і виконати все там (якщо потрібно) за допомогою стека, одне мікрозавдання за іншим, поки черга мікрозадач не буде порожньою або не потребує виконання, і ТОЛЬКО потім перевірити стек макрос
  • перевірити стек макрос і виконати все там (якщо потрібно) за допомогою стека

Стек Mico не буде дотиком, якщо стек не порожній. Стек макросів не буде дотиком, якщо мікросклад не порожній АБО не потребує виконання.

Підводячи підсумок: черга мікрозадач майже така ж, як і черга макрозадач, але ті завдання (process.nextTick, Promises, Object.observe, MutationObserver) мають більший пріоритет, ніж макрозадачі.

Мікро - як макрос, але з більш високим пріоритетом.

Тут у вас є "остаточний" код для розуміння всього.

console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
    setTimeout(() => {
        console.log('stack [4]')
        setTimeout(() => console.log("macro [5]"), 0);
        p.then(() => console.log('micro [6]'));
    }, 0);
    console.log("stack [7]");
});

console.log("macro [8]");

/* Result:
stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]

macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different


stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]

macro [5], macro [5], macro [5]

more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/

1
Виклик черги в стек - це зовсім заплутаність.
Бергі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.