Я уже упоминал разницу между синхронным и асинхронным поведением в JavaScript с этим ответом на ваш другой вопрос. Я постараюсь дать вам больше подробностей с этим ответом.
Там я рекомендовал вам посмотреть пару выступлений на эту тему. доклад Филиппа Робертса, другой доклад Джейка Арчибальда или блог Джейка , который объясняет то же самое. Я постараюсь обобщить все это.
Весь код JavaScript является синхронным и выполняется в одном потоке. Это означает, что есть один стек вызовов, и он может делать что-то одно за раз. Чтобы лучше понять время выполнения JavaScript, взгляните на это изображение, взятое с MDN .
Давайте попробуем go через ваш второй пример, чтобы увидеть, что происходит. Когда вызывается asyncFunction()
, он помещается в стек (Джейк называет их задачами, но на основе образа MDN они являются кадрами). Затем вызывается console.log('Before callback')
и он помещается в стек поверх текущего кадра. Так что теперь есть console.log
сверху и asyncFunction
снизу.
console.log
записывает эту строку в консоль. Затем он удаляется (выталкивается) из стека. asyncFunction
теперь на вершине стека. Теперь вызывается callbackFunction()
и он помещается в стек, который затем вызывает console.log('called callback')
, который также помещается в стек.
Теперь в стеке есть три функции: asyncFunction
в внизу, callbackFunction
и console.log
вверху. Когда console.log
заканчивает свою работу, он выталкивается из стека, и теперь callbackFunction
также заканчивается, и тот также извлекается из стека. Теперь вызывается console.log('After callback')
, помещается в стек и извлекается после выполнения, что означает, что asyncFunction
завершено и может быть извлечено из стека.
Здесь все заканчивается, стек пуст, нет больше кадров на нем. Исходя из этого и разговоров по ссылке выше, в этом примере нет ничего асинхронного. Шаг за шагом выполняется JS во время выполнения, и здесь не делаются асинхронные переходы. Но как нам достичь параллелизма в JS и нужен ли он нам? Цитируя Филиппа:
Причина, по которой мы можем делать вещи одновременно, заключается в том, что браузер - это больше, чем просто среда выполнения.
Именно поэтому мы можем использовать setTimeout(() => { doSomething(); }, 5000)
который будет ждать 5 (000 тысяч милли) секунд, не блокируя (замораживая) веб-страницу в течение этого времени. Что происходит, когда вызывается setTimeout
? Браузер запускает другой поток, который работает в параллельно . Работа потока состоит в том, чтобы ждать только 5 секунд. Но теперь становится интересно, что происходит, когда время истекло.
Для предотвращения одновременных изменений, которые могут привести к неожиданному поведению, в браузерах есть механизм очереди. Это позволяет потоку, созданному setTimeout
, отправлять ему сообщение, и в этом случае сообщение является функцией, переданной в setTimeout
, которая будет выполнена после обработки сообщения.
Но когда сообщения обработаны? Ну, только после того, как никакие кадры (задачи) не сложены. Это означает, что сообщения ожидают очистки стека после завершения всех кадров, чтобы их можно было обработать. Сообщения обрабатываются по одному за один l oop. Как только сообщение принимается в качестве задачи, оно становится обычным кодом JS, который выполняется в том же потоке с теми же правилами для стека проталкивания и выталкивания. Любые другие потенциальные сообщения, находящиеся в очереди в это время, должны ждать обработки текущего сообщения / кадра.
setTimeout
и setInterval
являются частями WebAPI s. Многие (если не все) имеют асинхронные обратные вызовы, и некоторые примеры включают: события DOM, события XHR, события Fetch, веб-работники, веб-сокеты, Promises, обратные вызовы MutationObserver и так далее. Последние два (Promises и MutationObservers) планируют задачи в другой очереди (очереди микрозадач), но они все еще асинхронны.
Разница между сообщениями, для которых задана очередь микрозадач и обычная (или макрозадача) очередь, состоит в том, что сообщения из очередей макрозадач принимаются по одному за событие l oop (что означает, что для всего l oop требуется до go между двумя сообщениями обрабатываются), в то время как сообщения из очередей микрозадач принимаются сразу после очистки стека. Это означает, что они имеют более высокий приоритет, чем те, которые находятся в очередях макросов. Для получения дополнительной информации, пожалуйста, смотрите / читайте доклад / блог Джейка.
На мой взгляд, обратный вызов является асинхронным при использовании с setTimeout () или setInterval ().
Да, но не только с setTimeout()
или setInterval()
. Если вы установите функцию обратного вызова на XMLHttpRequest
onreadystatechange
, она все равно будет вызываться асинхронно.
Обратите внимание, что могут быть другие API-интерфейсы, для которых требуется набор параметров функции, отличающийся от описанных в этих примерах, - фактически эти примеры вообще не используют параметры функции.