Прежде всего, когда вы дойдете до точки останова, которую вы нажали в second
, first
уже выполнено и больше не находится в стеке.
Когда мы входим в first
, мы сразу же набираем await asyncFunction()
. Это заставляет JavaScript вызывать asyncFunction, а затем свободно искать что-то еще, пока мы ждем его завершения. Что делает Javascript?
Ну, во-первых, мы должны разобраться asyncFunction
. Мы бросаем этот вызов функции в цикл обработки событий. То есть мы помещаем это в очередь «дел, когда у нас есть время». Мы вернемся к нему, когда он будет закончен.
Теперь нам нужно найти что-нибудь еще, связанное с нашим свободным временем. JavaScript не может продолжить со следующей строки first
(то есть, console.log («первый завершен»)), потому что наш await
означает, что мы не можем перейти к следующей строке, пока не закончится asyncFunction
.
Итак, мы ищем стек. Javascript видит, что first
был вызван с основного, а first
сам не был await
изд. Другими словами, мы сказали Javascript, что не имеет значения, если первый асинхронный, просто продолжайте независимо. Итак, мы переходим прямо к second
. Выполнив second
, мы оглядываемся назад на то, что называется, и продолжаем выполнение так, как все ожидают.
Затем, в какой-то момент в будущем наш asyncFunction
заканчивается. Возможно, ожидание вызова API для возврата. Возможно это ожидало в базе данных, чтобы ответить на это. Чего бы он ни ждал, сигнал отправлен, и с ним можно разобраться. Он не "вызывается" из main
- внутренне функция вызывается обратно, что-то вроде обратного вызова, но принципиально вызывается с совершенно новым стеком, который будет уничтожен, как только мы закончим, вызывая оставшуюся часть функции.
Учитывая, что мы находимся в новом стеке и давно покинули «основной» кадр стека, как main
и first
снова окажутся в стеке, когда мы достигнем точки останова внутри него?
В течение долгого времени, если вы запускали свой код в отладчиках, простой ответ заключался в том, что они не будут. Вы просто получите функцию, в которой находитесь, и отладчик скажет вам, что она была вызвана из «асинхронного кода» или чего-то подобного.
Однако в настоящее время некоторые отладчики могут следовать ожидаемому коду обратно к обещанию, которое он разрешает (помните, await
и async
в основном просто синтаксический сахар поверх обещаний). Другими словами, когда ваш ожидаемый код завершается и «обещание» скрывается, ваш отладчик услужливо выясняет, как «должен» выглядеть стек. То, что он показывает, на самом деле не очень похоже на то, как механизм в конечном итоге вызвал функцию - в конце концов, она была вызвана из цикла событий. Тем не менее, я думаю, что это полезное дополнение, позволяющее всем нам держать ментальную модель нашего кода намного проще, чем то, что происходит на самом деле!
Некоторое дальнейшее чтение о том, как это работает, которое охватывает гораздо больше деталей, чем я могу здесь: