Схема ответа
- Давайте сначала поговорим об основных знаниях, что такое макрозадачи и микрозадачи?
- Расскажите о процессе работы механизма событийного цикла и во время разговора нарисуйте картинку.
- Обратите внимание на порядок выполнения async/await.Хром можно оптимизировать.Практика на самом деле незаконная.Пиар команды V8 самоуверенный.Видно,что вы очень учитесь,разбираетесь очень подробно и досконально.
- Давайте также поговорим о цикле событий узла Повторите пункты 1, 2 и 3. Третья точка в узле — это точка изменения цикла событий до и после узла 11.
Давайте последуем этой схеме ниже и поговорим о каждом пункте~
цикл событий в браузере
Во время выполнения кода JavaScript, помимо использования стека вызовов функций для получения порядка выполнения функций, он также использует очередь задач для получения выполнения других кодов. Весь процесс выполнения мы называем процессом цикла событий. В потоке цикл событий уникален, но в очереди задач их может быть несколько. Очередь задач далее делится на макро-задачи (macro-задачи) и микро-задачи (микрозадачи), в последнем стандарте они называются задачами и заданиями соответственно.
макрозадача примерно включает в себя:
- скрипт (общий код)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
Микрозадача, вероятно, включает в себя:
- process.nextTick
- Promise
- Async/Await (фактически обещание)
- MutationObserver (новая функция html5)
Для общего исполнения я нарисовал блок-схему:
总的结论就是,执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。 Например:
В сочетании с пониманием блок-схемы вывод ответа: async2 end => Promise => async1 end => promise1 => promise2 => setTimeout Однако для async/await у нас есть одна деталь, с которой нужно разобраться. следующим образом:
асинхронный/ожидающий порядок выполнения
мы знаемasync
Функция, которая неявно возвращает Promise в качестве результата, может быть просто понята так: когда функция, следующая за await, выполняется, await генерирует микрозадачу (Promise.then — это микрозадача). Но стоит обратить внимание на тайминг этой микрозадачи, после выполнения await она выскакивает из асинхронной функции и выполняет другой код (вот работа сопрограммы, А приостанавливает выполнение, а управление передается Б ). После выполнения другого кода вернитесь к асинхронной функции, чтобы выполнить остальную часть кода, а затем зарегистрируйте код ожидания в очереди микрозадач. Давайте посмотрим на пример:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// 旧版输出如下,但是请继续看完本文下面的注意那里,新版有改动
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout
Проанализируйте этот код:
- выполнить код, вывести
script start
. - Выполните async1(), вызовите async2() и затем выведите
async2 end
, В это время контекст функции async1 будет сохранен, а затем функция async1 будет выскочить. - Когда встречается setTimeout, создается задача макроса
- Выполнить обещание, вывод
Promise
. Затем столкнитесь, сгенерируйте первую микрозадачу - Продолжайте выполнять код, вывод
script end
- После выполнения логики кода (выполняется текущая макрозадача) начинает выполняться очередь микрозадач, сгенерированная текущей макрозадачей, и вывод
promise1
, затем встречается микрозадача и генерируется новая микрозадача - Выполнить полученную микрозадачу, вывод
promise2
, выполняется текущая очередь микрозадач. Выполнение обратно в async1 - Выполнение await фактически сгенерирует возврат обещания, т.е.
let promise_ = new Promise((resolve,reject){ resolve(undefined)})
Когда выполнение будет завершено, выполните оператор, следующий за ожиданием и выводомasync1 end
- Наконец, выполните следующую задачу макроса, то есть выполните setTimeout, вывод
setTimeout
Уведомление
Новая версия браузера Chrome не печатает, как указано выше, потому что Chrome оптимизирован, ожидание становится быстрее, и вывод:
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
Но такая практика на самом деле противоречит норме, и норму, конечно, можно изменить, это часть команды V8.PR, текущая версия печати изменена. Есть также связанные обсуждения на Zhihu, вы можете взглянутьWoohoo. Call.com/question/26...
Мы можем понять это в двух случаях:
-
Если за ожиданием непосредственно следует переменная, например: Затем выйдите из функции async1 и выполните другие коды.Когда встречается функция обещания, функция promise.then() будет зарегистрирована в очереди микрозадач.Обратите внимание, что микрозадача, стоящая за ожиданием, уже существует в очереди микрозадач в это время. Таким образом, в этом случае сначала выполняется код после ожидания (окончание async1), а затем код микрозадачи (обещание1, обещание2), зарегистрированный после выполнения функции async1.
-
Если за ожиданием следует вызов асинхронной функции, такой как приведенный выше код, измените код на этот:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
console.log('async2 end1')
})
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
Результат:
// script start => async2 end => Promise => script end => async2 end1 => promise1 => promise2 => async1 end => setTimeout
В это время после выполнения await код после await сначала не прописывается в очередь микрозадачи, а после выполнения await сразу выскакивает функция async1 и выполняются другие коды. Затем, когда вы встретите промис, зарегистрируйте promise.then как микрозадачу. После выполнения другого кода необходимо вернуться к функции async1 для выполнения оставшегося кода, а затем зарегистрировать код после ожидания в очереди микрозадач.Обратите внимание, что в это время в очереди микрозадач уже есть ранее зарегистрированные микрозадачи. Таким образом, в этом случае сначала будут выполняться микрозадачи (обещание1, обещание2) вне функции async1, а затем будут выполняться микрозадачи, зарегистрированные в async1 (окончание async1). Можно понять, что в этом случае код после await будет выполняться в конце текущего цикла. В браузере, а также в узле есть цикл событий. Цикл событий — это механизм, с помощью которого узел обрабатывает неблокирующие операции ввода-вывода. Реализация цикла событий в узле зависит от механизма libuv. Начиная с узла 11 изменились некоторые принципы цикла событий, здесь мы поговорим о новом стандарте, и, наконец, перечислим точки изменения, чтобы каждый мог понять причину и следствие.
Контур событий в узле
В браузере, а также в узле есть цикл событий. Цикл событий — это механизм, с помощью которого узел обрабатывает неблокирующие операции ввода-вывода. Реализация цикла событий в узле зависит от механизма libuv. Начиная с узла 11 изменились некоторые принципы цикла событий, здесь мы поговорим о новом стандарте, и, наконец, перечислим точки изменения, чтобы каждый мог понять причину и следствие.
Макрозадачи и микрозадачи
В узле также есть макро-задачи и микро-задачи, аналогичные циклу обработки событий в браузере, где
макрозадача примерно включает в себя:
- setTimeout
- setInterval
- setImmediate
- скрипт (общий код)
- операции ввода-вывода и т.д.
Микрозадача примерно включает в себя:
- Process.nextTick (Micro отличается от обычной задачи, выполняется до выполнения задач Queue Micro)
- Новый промис().ТОМ(обратный вызов) и т.д.
Общее понимание цикла событий узла
Сначала посмотрите на упрощенную схему цикла событий узла на официальном сайте:
Каждый блок на диаграмме называется этапом механизма цикла событий, и каждый этап имеет очередь FIFO для выполнения обратных вызовов. Хотя каждый этап является особенным, обычно, когда цикл событий входит в определенный этап, он будет делать все, что характерно для этого этапа, а затем выполнять обратные вызовы в очереди этого этапа до тех пор, пока очередь не будет исчерпана или не будет исчерпано максимальное количество выполненных обратных вызовов. Когда очередь исчерпана или достигнут предел обратного вызова, цикл событий перейдет к следующему этапу.
Следовательно, из приведенной выше упрощенной схемы мы можем проанализировать последовательность этапов цикла событий узла как:
Этап входных данных (входящие данные) -> этап опроса (poll) -> этап проверки (проверка) -> этап обратного вызова события закрытия (обратный вызов закрытия) -> этап обнаружения таймера (таймеры) -> этап обратного вызова события ввода-вывода (I/ О, обратные вызовы) -> этап ожидания (ожидание, подготовка) -> этап опроса...
Обзор сцены
- Этап обнаружения таймера (таймеры): на этом этапе выполняется обратный вызов таймера, то есть функции обратного вызова в setTimeout и setInterval.
- Фаза обратного вызова события ввода-вывода (обратные вызовы ввода-вывода): выполнение обратных вызовов ввода-вывода, которые отложены до следующей итерации цикла, то есть некоторые обратные вызовы ввода-вывода, которые не были выполнены в предыдущем раунде циклов.
- Стадия простоя (ожидание, подготовка): используется только внутри системы.
- Фаза опроса (опрос): получение новых событий ввода-вывода; выполнение обратных вызовов, связанных с вводом-выводом (почти во всех случаях, за исключением обратных вызовов завершения работы, тех, которые отправляются таймерами и setImmediate() ), в остальных случаях узел case будет блокироваться здесь, когда соответствующий.
- Фаза проверки (проверка): здесь выполняется функция обратного вызова setImmediate()
- Сцена обратного вызова События (Закрыть обратный вызов): Некоторые тесные функции обратного вызова, такие как: Socket.on («Закрыть», ...).
Три ключевых этапа
Большинство асинхронных задач в ежедневной разработке обрабатываются на трех этапах опроса, проверки и таймеров, поэтому давайте сосредоточимся на нем.
timers
Этап таймеров выполняет обратные вызовы setTimeout и setInterval и управляется этапом опроса. Точно так же время, указанное таймером в узле, не является точным временем, оно может быть выполнено только как можно скорее.
poll
Опрос является ключевым этапом.Логическая схема выполнения этапа опроса выглядит следующим образом:
Если таймер уже существует и срок его действия истек, выньте его и выполните, и цикл обработки событий вернется к этапу таймеров.
Если таймера нет, он просматривает очередь функций обратного вызова.
-
Если очередь опроса не пуста, очередь обратного вызова будет проходиться и выполняться синхронно до тех пор, пока очередь не станет пустой или не будет достигнут системный предел.
-
Если очередь опроса пуста, происходят две вещи
- Если необходимо выполнить обратный вызов setImmediate, фаза опроса остановится и перейдет к фазе проверки для выполнения обратного вызова.
- Если обратный вызов setImmediate не выполняется, он будет ждать добавления обратного вызова в очередь и немедленно выполнит обратный вызов.Также будет установлен тайм-аут, чтобы предотвратить его ожидание, и он автоматически перейдет в фазу проверки после Период времени.
check
этап проверки. Это относительно простой этап, непосредственно выполняющий обратный вызов setImmdiate.
process.nextTick
process.nextTick — это очередь задач, независимая от eventLoop.
После завершения каждой фазы eventLoop будет проверяться очередь nextTick, и если в ней есть задачи, то эта часть задач будет иметь приоритет над выполнением микрозадачи.
См. пример:
setImmediate(() => {
console.log('timeout1')
Promise.resolve().then(() => console.log('promise resolve'))
process.nextTick(() => console.log('next tick1'))
});
setImmediate(() => {
console.log('timeout2')
process.nextTick(() => console.log('next tick2'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));
- Перед node11, так как каждый этап eventLoop завершен, будет проверяться очередь nextTick.Если в ней есть задача, то этой части задачи будет отдан приоритет выполнения микрозадачи.Поэтому приведенный выше код сначала входит в очередь check stage, выполняет все setImmediate и выполняет nextTick после завершения Queue, очередь микрозадач выполняется в конце, поэтому вывод
timeout1=>timeout2=>timeout3=>timeout4=>next tick1=>next tick2=>promise resolve
- После node11 process.nextTick является своего рода микрозадачей, поэтому вышеприведенный код сначала входит в фазу проверки, выполняет макрозадачу setImmediate, затем выполняет свою очередь микрозадач, а затем выполняет следующую макрозадачу и ее микрозадачу, поэтому вывод
timeout1=>next tick1=>promise resolve=>timeout2=>next tick2=>timeout3=>timeout4
Описание различий версий узлов
Основным описанием здесь является разница до и после node11, потому что некоторые функции после node11 были согласованы с браузером.Короче говоря, общее изменение заключается в том, что если версия node11 выполняет задачу макроса (setTimeout, setInterval и setImmediate) в одном stage ) и немедленно выполнить соответствующую очередь микрозадач, давайте посмотрим~
Время выполнения таймеров фазы изменяется
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
- Если версия node11 выполняет задачу макроса (setTimeout, setInterval и setImmediate) на этапе, очередь микрозадач выполняется немедленно, что согласуется с операцией на стороне браузера, и окончательный результат
timer1=>promise1=>timer2=>promise2
- Если у Node10 его предыдущая версия для выполнения первого таймера второй таймер завершен в очереди.
- Если второй таймер еще не находится в очереди завершения, окончательный результат
timer1=>promise1=>timer2=>promise2
- Если второй таймер уже находится в очереди завершения, окончательный результат
timer1=>timer2=>promise1=>promise2
- Если второй таймер еще не находится в очереди завершения, окончательный результат
Изменения в сроках выполнения этапа проверки
setImmediate(() => console.log('immediate1'));
setImmediate(() => {
console.log('immediate2')
Promise.resolve().then(() => console.log('promise resolve'))
});
setImmediate(() => console.log('immediate3'));
setImmediate(() => console.log('immediate4'));
- Если это версия после node11, она выведет
immediate1=>immediate2=>promise resolve=>immediate3=>immediate4
- Если это версия до node11, она выведет
immediate1=>immediate2=>immediate3=>immediate4=>promise resolve
Время выполнения очереди nextTick изменяется
setImmediate(() => console.log('timeout1'));
setImmediate(() => {
console.log('timeout2')
process.nextTick(() => console.log('next tick'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));
- Если это версия после node11, она выведет
timeout1=>timeout2=>next tick=>timeout3=>timeout4
- Если это версия до node11, она выведет
timeout1=>timeout2=>timeout3=>timeout4=>next tick
В приведенных выше примерах вы должны четко чувствовать его изменения.В любом случае, помните вывод.Если версия node11 выполняет макрозадачу (setTimeout, setInterval и setImmediate) на этапе, она немедленно выполнит соответствующую микрозадачу. .
Основное различие между узлом и браузером eventLoop
Основное различие между ними заключается в том, что микрозадачи в браузере выполняются в каждой соответствующей макрозадаче, а микрозадачи в nodejs выполняются между разными этапами.
Больше информации для понимания
- [Серия статей о решении проблем с голосом] Расскажите о механизме событийного цикла JS (включая навыки ответа на полный балл)
- [Серия статей о решении проблем с голосом] Расскажите о механизме событийного цикла JS (включая навыки ответа на полный балл)
- вопросы самооценки
использованная литература
- предварительное интервью
- Углубленное размышление о цикле событий узла, вызванном вопросом интервью
- Цикл событий Node.js, таймеры и process.nextTick()
- Подробно объясните механизм цикла событий в JavaScript.
- New Changes to the Timers and Microtasks in Node v11.0.0 ( and above)
наконец
- Добро пожаловать, чтобы добавить меня в WeChat (winty230), привлечь вас в техническую группу, долгосрочный обмен и обучение...
- Добро пожаловать, чтобы обратить внимание на «Front-end Q», серьезно изучить интерфейс и быть профессиональным техническим специалистом...