Как цикл обработки событий Javascript управляет выполнением вызова неблокирующей функции после того, как событие было исключено из очереди? - PullRequest
0 голосов
/ 26 января 2019

Допустим, в стеке вызовов есть 5 вещей и один элемент в очереди событий. Как только все 5 элементов извлекаются из стека вызовов, обратный вызов из очереди событий помещается в стек вызовов (выполнение может занять 20 секунд). Тем временем я добавил еще один (не блокирующий) вызов в стек вызовов. Как это работает, если интенсивная операция ввода-вывода все еще выполняется? Система временно зависает?

1 Ответ

0 голосов
/ 26 января 2019

Как вы сказали, это цикл, или, как сказано в спецификации JavaScript, очередь заданий . Первоначальное выполнение сценария верхнего уровня - это задание; обратный вызов обработчика событий - это работа; таймер обратного вызова - это работа; и т.д.

Когда задание выбирается из очереди заданий, оно запускается до завершения¹. Если это занимает 20 секунд, это займет 20 секунд. Поток, обрабатывающий эту очередь заданий, больше ничего не может сделать в течение этих 20 секунд. Если вы сделаете это в главном потоке пользовательского интерфейса в веб-браузере, он в значительной степени замораживает пользовательский интерфейс браузера. (Если вы делаете это в рабочем потоке, конечно, он просто блокирует рабочий поток.) ​​

Я спросил, что вы имели в виду, добавив (неблокирующий) вызов в стек вызовов. Вы сказали:

Допустим, вы выполняете какую-то операцию, например нажатие кнопки, которая добавляет еще один вызов в стек вызовов.

Нажатие кнопки с обработчиком события не добавляет вызов в стек вызовов; это добавляет работу в очередь заданий. (Вызов функции foo(); в коде выполнения задания добавляет вызов в стек вызовов.) Если поток занят обработкой другого задания, то это задание находится там, ожидая, когда его завершат.

Следует отметить, что существует два стандартных вида заданий: задания сценариев и задания обещаний. (Или, как их называет спецификация HTML, задачи и микрозадачи.) Основное выполнение сценария, обратные вызовы событий DOM и обратные вызовы таймера - это все задания / задачи сценария (также называемые «макрозадачами»). Обратные вызовы для разрешения обещаний - это обещанные задания / микрозадачи. Разница заключается в том, что при выполнении задания (задачи) сценария все запланированные задания (микрозадачи), которые он планирует, будут запускаться по окончании задания, а не добавляться в основную очередь заданий. Любые задания обещания, запланированные заданием обещания, запускаются во время той же самой обработки конца сценария. То есть задания / микрозадачи обещаний имеют более высокий приоритет, чем задания / задачи сценариев.

Вы можете видеть, что происходит здесь:

// This script is running in a script job / task

// Here, we schedule a script job / task for an immediate timer callback:
setTimeout(() => {
  console.log("Timer");
}, 0);

// After doing that, we schedule a promise resolution callback:
Promise.resolve().then(() => {
  console.log("Promise resolution 1");
  Promise.resolve().then(() => {
    console.log("Promise resolution 2");
  });
});

// And just for emphasis, we'll output something before either happens
console.log("Main script");

Вывод:

Main script
Promise resolution 1
Promise resolution 2
Timer

... хотя таймер был запланирован до первого обратного вызова разрешения и, конечно, до второго.

Я говорю два «стандартных» вида заданий / заданий, потому что среды предоставляют другие вещи (например, setImmediate Node.js или requestAnimationFrame браузера), которые несколько отличаются от двух основных типов заданий / типов очередей.


¹ Поток можно приостановить с помощью Atomics.wait, но его нельзя использовать для обработки другого задания из очереди. Большинство механизмов JavaScript не позволяют приостанавливать основной поток (поток пользовательского интерфейса в браузерах, основной поток в Node.js), но допускают приостановку рабочих потоков.

...