#javascript #event-loop #task-queue
#javascript #цикл событий #очередь задач
Вопрос:
Я изучаю стек выполнения, очередь задач и механизм цикла событий.
Я непрерывно нажимал кнопку до тех пор, пока не будет доступен основной поток (непосредственно перед выполнением функции a ()), как показано ниже.
Я думал, что события click (UI) и setTimeout используют одну и ту же очередь, называемую Macrotask или очередь задач, поэтому, когда я нажимаю кнопку между 1 и 3 секундами, я думал, что журнал задач2 печатается между task1 и task2. Но результат был не таким. Задача 2 (событие щелчка) всегда печатается первой, а события setTimeout (задача 1, задача 3) печатаются после событий щелчка.
Поэтому мне интересно, используют ли события щелчка другой механизм очереди, чем setTimeout, или событие щелчка имеет приоритет над setTimeout.
заранее благодарю вас за помощь
Операция
- нажмите кнопку (task2)
- ———1000 мс setTimeout задача1———
- нажмите кнопку (task2)
- ———задача setTimeout на 3000 мс3———
- нажмите кнопку (task2)
- ———теперь доступен основной поток 6000 мс———
Мой порядок регистрации ожиданий
fn a done
task2 (click) done
task1 (setTimeout 1000ms) done
task2 (click) done
task3 (setTimeout 3000ms) done
task2 (click) done
Порядок регистрации результатов
fn a done
task2 (click) done
task2 (click) done
task2 (click) done
task1 (setTimeout 1000ms) done
task3 (setTimeout 3000ms) done
Код
const btn = document.querySelector('button');
btn.addEventListener('click', function task2() {
console.log('task2 (click) done');
});
function a() {
setTimeout(function task1() {
console.log('task1 (setTimeout 1000ms) done');
}, 1000);
setTimeout(function task3() {
console.log('task3 (setTimeout 3000ms) done');
}, 3000);
// hold main thread for 6000ms(*1)
const startTime = new Date();
while (new Date() - startTime < 6000);
console.log('fn a done');
}
a();
<button>button</button>
<script src="main.js"></script>
Комментарии:
1. щелчок не имеет ничего общего с очередью
Ответ №1:
Я думал, что события click (UI) и setTimeout используют одну и ту же очередь
Они этого не делают.
События пользовательского интерфейса используют источник задач взаимодействия с пользователем (UI), который в большинстве браузеров имеет свою собственную очередь задач, setTimeout
использует источник задач таймера, который также имеет свою очередь задач в большинстве браузеров.
Хотя это не требуется спецификациями, источник задачи пользовательского интерфейса имеет один из самых высоких приоритетов из всех источников задач почти во всех браузерах, а таймеры имеют один из самых низких.
Принцип работы этой расстановки приоритетов заключается в том, что на первом шаге модели обработки цикла событий агент пользователя (UA) должен выбрать в одной из своих очередей событий, какую задачу он будет выполнять.
Примечание: то, что в просторечии называется «макрозадачей», — это любая задача, которая не является микрозадачей.
Очередь микрозадач не является очередью задач, и хотя микрозадача может быть выбрана в качестве основной задачи на первом этапе обработки цикла событий, очередь микрозадач не может быть приоритетной, поскольку она должна быть очищена синхронно на каждой контрольной точке микрозадачи, что может происходить несколько раз во время каждой итерации цикла событийи особенно после выполнения выбранной задачи.
Итак, здесь, когда while
цикл завершается, блокируя цикл событий, и начинается следующая итерация, UA должен будет выбрать, из какой очереди задач он должен выбрать следующую задачу. Он увидит в своей очереди задач пользовательского интерфейса, что ожидаются новые события, и выполнит их, потому что они имеют более высокий приоритет.
Затем, когда все они будут выполнены, он выберет очередь таймеров и выполнит их в порядке их запланированного времени.
Также обратите внимание, что у них есть система голодания, которая не позволяет высокоприоритетной очереди задач блокировать другие очереди задач слишком долго.
Наконец, я должен упомянуть, что есть предложение позволить нам, веб-разработчикам, напрямую решать все эти вопросы с расстановкой приоритетов: планирование основного потока.
Используя эту экспериментальную функцию, мы могли бы переписать фрагмент как
if( !("scheduler" in window) ) {
console.error("Your browser doesn't support the postTask API");
console.error("Try enabling the Experimental Web Platform features in chrome://flags");
}
else {
scheduler.postTask(() => {
console.log('task1 (background) done');
}, { priority: "background" } );
scheduler.postTask(() => {
console.log('task2 (background) done');
}, { priority: "background" } );
// hold main thread for 6000ms(*1)
const startTime = new Date();
while (new Date() - startTime < 2000);
scheduler.postTask(() => {
console.log('task3 (user-blocking) done');
}, { priority: "user-blocking" } );
console.log('synchronous done');
}
И убедитесь, что последняя задача выполняется первой.
Комментарии:
1. Спасибо! Ваш ответ очень помогает мне понять очередь задач и механизм цикла событий!
2. @Kaiido Не могли бы вы ответить на следующий вопрос? Вы написали, что
setTimeout uses the timer task-source
да, это говорит нам о специальной очереди задач для таймеров, поэтому мы видим определение задачи для таймера: html.spec.whatwg.org/multipage /. … Но как насчет событий, где те же шаги задачи события ?3. @MaximPro html.spec.whatwg.org/multipage /…
4. @Kaiido но это просто описание задач события. Вы видели мою ссылку, связанную с step? Я ожидал увидеть что-то подобное. Я имею в виду, что мы можем легко увидеть создание задачи setTimeout, и я хочу увидеть, как это происходит с событием.
5. Предоставленная мной ссылка ссылается на спецификации событий пользовательского интерфейса W3C , которые определяют это. К сожалению, они не заходят так далеко, как определение того, как запланирована задача, нет.
Ответ №2:
Давайте просто посмотрим, что вы делаете с циклом setTimeout и while.
Если вы запустите фрагмент кода, вы увидите, что временная метка для таймаута в 1 секунду и 3 секунды в основном одинакова. Это связано с тем, что цикл while блокирует основной поток на 6 секунд, пока он не станет доступен для запуска функций в setTimeout .
Обратные вызовы выполняются не точно через 1 секунду или 3 секунды, а только тогда, когда основной поток свободен. setTimeout гарантирует выполнение функции обратного вызова минимум через x миллисекунд, что составляет> = 1 секунда для задачи 1 и> = 3 секунды для задачи 3.
function a() {
setTimeout(function task1() {
console.log('task1 (setTimeout 1000ms) done ' new Date());
}, 1000);
setTimeout(function task3() {
console.log('task3 (setTimeout 3000ms) done ' new Date());
}, 3000);
// hold main thread for 6000ms(*1)
const startTime = new Date();
while (new Date() - startTime < 6000);
console.log('fn a done');
}
a();