Что на самом деле делает «решимость»?
Вызов resolve(x)
делает три вещи.
Изменяет внутреннее состояние обещания на выполненное. Как только состояние изменяется на выполненное, оно не может снова изменить состояние. Это одностороннее постоянное изменение.
Устанавливает значение x
(независимо от того, какой единственный аргумент вы передаете для разрешения) в качестве разрешенного значения обещания (оно хранится внутри обещания). Если ничего не передано resolve()
, то разрешенное значение будет undefined
.
Он вставляет событие в очередь событий, чтобы вызвать обработчики .then()
этого текущего обещания, которые будут вызваны при наступающем цикле в цикле событий. Это планирует запуск обработчиков .then()
после завершения текущего потока выполнения Javascript.
Теперь, чтобы объяснить последовательность, которую вы видите в консоли. Сначала несколько вещей, которые помогут понять это.
- Функция исполнителя обещаний (обратный вызов, переданный
new Promise(fn)
) вызывается синхронно (в середине текущего потока выполнения).
- Когда срабатывает таймер
setTimeout()
(внутренний для механизма JS), обратный вызов таймера вставляется в очередь событий и включается в будущий цикл цикла событий.
- Когда обещание разрешается, событие вставляется в очередь событий и будет перенесено в будущий цикл цикла событий.
- В цикле событий имеется несколько типов очередей событий, и не все имеют одинаковый приоритет. В общем случае обещанные события будут получены до большинства других типов событий, хотя возможно, что это может немного отличаться в зависимости от реализации. Таким образом, когда несколько типов событий помещаются в очередь событий, поэтому они находятся там одновременно, это может влиять на то, какой из них вызывается первым.
- Множественные обработчики
.then()
или .catch()
, добавленные в очередь событий, обрабатываются в том порядке (относительно друг друга), в котором они изначально были запущены в режиме FIFO (первым пришел-первым вышел).
- Когда обещание соединяется с чем-то вроде
fn().then(f1).then(f2).then(f3)
, имейте в виду, что каждое .then()
возвращает новое обещание, которое будет иметь свое время, когда оно будет решено или отклонено, после предыдущего и в зависимости от того, что происходит в его обработчике. функция.
Итак, вот последовательность событий в вашем коде:
- Вызывается первая функция исполнителя обещаний, таким образом, вы получите вывод
1
- Таймер создается с таймаутом
0
. В какой-то момент очень скоро событие обратного вызова таймера будет добавлено в очередь событий.
- Вы звоните
resolve()
по первому обещанию. Это вставляет событие / задачу в очередь обещаний для вызова ее обработчиков .then()
в будущем цикле цикла событий. Остальная часть этой последовательности кода Javascript продолжает выполняться. Но обратите внимание, что в этом первом обещании еще нет обработчиков .then()
, поскольку его цепочечные методы .then()
еще не выполнены.
- Вы создаете второе обещание, и немедленно вызывается его функция-исполнитель, которая выводит
3
.
- Вы звоните
resolve()
по этому второму обещанию. Это вставляет событие / задачу в очередь обещаний для вызова ее обработчиков .then()
в будущем цикле цикла событий. Остальная часть этой последовательности кода Javascript продолжает выполняться.
.then()
призван на это второе обещание. Это регистрирует функцию обратного вызова обработчика .then()
во втором обещании (добавляет его во внутренний список) и возвращает новое обещание.
.then()
вызывается на этом вновь возвращенном обещании (третье обещание). Это регистрирует функцию обратного вызова обработчика .then()
в этом третьем обещании (добавляет его во внутренний список) и возвращает новое обещание.
.then()
вызывается на этом вновь возвращенном обещании (четвертое обещание). Это регистрирует функцию обратного вызова обработчика .then()
в этом четвертом обещании (добавляет его во внутренний список) и возвращает новое обещание. .then()
вызывается на этом вновь возвращенном обещании (пятое обещание).Это регистрирует функцию обратного вызова .then()
в этом пятом обещании (добавляет его во внутренний список) и возвращает новое обещание. - Функция executor из самого первого обещания наконец возвращает.
.then()
призван на самое первое обещание.Это регистрирует функцию обратного вызова обработчика .then()
в этом первом обещании (добавляет его во внутренний список) и возвращает новое обещание. - , поскольку обработчик
.then()
из второго обещания выполнялся до обработчика .then()
из первого обещания оно сначала помещается в очередь задач, и таким образом вы получаете выходные данные 4
затем. - Когда этот обработчик
.then()
запускается, он разрешает созданное ранее обещание, третье обещаниеи он добавляет задачу в очередь обещаний для запуска ее .then()
обработчиков. - Теперь следующим элементом в очереди задач является обработчик
.then()
из первого обещания, так что он получает шанс на запуск иВы видите вывод 5
. - Затем создается еще одно новое обещание с
new Promise(...)
и запускается его функция исполнителя.Это приводит к выводу 6
. - Это новое обещание разрешается с помощью
resolve()
. - Вызывается его
.then()
, который регистрирует обратный вызов .then()
и возвращает новое обещание. - Текущая последовательность Javascript выполнена, поэтому она возвращается к циклу событий для следующего события.Следующим, что было запланировано, был обработчик
.then()
для четвертого обещания, поэтому он извлекается из очереди событий, и вы видите вывод 9
. - Запуск этого обработчика
.then()
разрешил пятое обещание и вставил его обработчик .then()
в очередь задач обещания. - Назад в очередь событий для следующего события обещания.Там мы получаем обработчик
.then()
из конечного new Promise().then()
в коде, и вы получаете вывод 7
. - Вышеописанный процесс повторяется, и вы видите выходные данные
11
, затем 12
. - Наконец, очередь задач обещания пуста, поэтому цикл событий ищет другие типы событий, которые не имеют такого высокого приоритета, находит событие
setTimeout()
и вызывает его обратный вызов, и вы, наконец, получаете вывод2
.
Итак, setTimeout()
идет последним здесь по нескольким причинам.
- События Promise запускаются до событий таймера (в ES6), поэтому любые очереди ставятся в очередьСобытия обещаний передаются перед любым событием таймера в очереди.
- Поскольку все ваши обещания разрешаются без фактического ожидания завершения каких-либо других асинхронных событий (что не является реальным поведением и, как правило, как и почемукаждый использует обещания), таймер должен ждать, пока они все не закончат, прежде чем он получит шанс на запуск.
И еще несколько комментариев:
ВыяснениеОтносительный порядок запуска различных обработчиков .then()
в разных и независимых цепочках обещаний иногда возможен (это возможно только здесь, потому что нет реального асинхронного обещания, разрешающегося с неопределенным временем разрешения), но если вам действительно нужен определенный порядок выполнения, тогда лучшепросто цепочка ваших операций, чтобы явно указать порядок, в котором вы хотите, чтобы вещи выполнялись в коде.Это устраняет любую зависимость от мелких деталей реализации локального движка Javascript и делает код более понятным.Другими словами, кто-то, читающий ваш код, не должен пройти 22 шага, которые я перечислил, чтобы следовать желаемому порядку выполнения.Вместо этого код будет просто указывать порядок путем прямого обещания.
В реальном коде необычно иметь потерянные, отключенные цепочки обещаний, которые вы создаете внутри обработчиков .then()
.Поскольку вы не возвращаете эти обещания из обработчика .then()
и, следовательно, вставляете их в родительскую цепочку обещаний, невозможно передать результаты или ошибки из этих отключенных цепочек обещаний.Хотя иногда есть причина кодировать операцию «забей и забудь», которая вообще не нуждается в связи с внешним миром, это необычно и, как правило, является признаком проблемного кода, который не распространяет ошибки должным образом и чьирезультаты не синхронизированы должным образом с остальными событиями.
когда я ставлю 'решить' позади, он печатает то же самое!
Как вы обнаружили, это ничего не меняет..then()
после new Promise(...)
не выполняется до тех пор, пока функция исполнителя не завершит работу и не вернется, поэтому на самом деле не имеет значения, где внутри исполнителя вы вызываете resolve()
.Иными словами, ни один из обработчиков .then()
не может быть зарегистрирован до тех пор, пока исполнитель обещания не вернется, поэтому независимо от того, где вы вызываете resolve()
в исполнителе обещания, результат будет одинаковым.