Давайте возьмем этот пример кода:
Promise.resolve("resolved").then(i => console.log("resolved"));
console.log("start");
Это немедленное (синхронно) разрешающее обещание. Когда метод then
выполняется, он уже находится в разрешенном состоянии. Тем не менее, обратный вызов then
не выполняется немедленно. Вместо этого задание помещается в очередь заданий (микрозадача). Таким образом, мы получаем этот вывод:
start
resolved
Ваш вопрос состоит в том, мог ли бы он работать без такого планирования заданий, то есть обратный вызов был бы выполнен немедленно, так что результат будет:
resolved
start
С примером кода это действительно будет реальной альтернативой. Но теперь возьмем другой пример, где обещание разрешается не кодом JavaScript, а событием более низкого уровня, глубоко скрытым в API, который инкапсулирует это с интерфейсом обещания. Допустим, это некоторый API для чтения содержимого файла:
readFile(path).then((content) => console.log("file contents: " + content));
console.log("start of a lot of work");
for (let i = 0; i < 1e7; i++) {
// some work
}
console.Log("end of a lot of work");
Итак, согласно спецификации Promise это выдает:
start of a lot of work
end of a lot of work
file contents: .....
Теперь предположим на мгновение, что цикл работы занимает 3 секунды, и что API чтения файлов имеет реализацию, которая делегирует большую часть работы функциям операционной системы. Эти функции ОС будут работать в соответствии с архитектурой обработки ОС, которая часто является некой формой многозадачности, управляемой событиями. Суть здесь в том, что ОС сможет выполнять файловые операции, в то время как цикл JS также выполняется.
Давайте также предположим, что чтение файла завершается в течение 1 секунды. Событие переходит из ОС в промежуточное программное обеспечение API, не относящееся к JavaScript, готовое всплыть в «мир» JavaScript. Что должно произойти сейчас? Мы можем представить два варианта:
В очередь заданий помещается задание, которое сигнализирует о том, что обещание выполнено и необходимо вызвать обратные вызовы then
. Вот как это на самом деле работает.
Работающий цикл JavaScript прерван , обещание, возвращаемое readFile
, разрешается, и в этот момент вызывается обратный вызов then
. После завершения обратного вызова контекст выполнения, который был прерван, восстанавливается и продолжается до завершения.
Последний вид механики может привести к целой цепочке прерываний, когда даже код в then
обратном вызове может быть прерван еще одним событием разрешения обещания, ... и т. Д. Это может привести к созданию контекстов выполнения, которые должны поддерживаться как стек. Это сделало бы выполнение кода довольно непредсказуемым, трудным для отладки и уязвимым для переполнения стека.
По моему мнению, это причины, почему этот вариант нежелателен, и почему вариант 1 - это то, что мы имеем. И хотя второй вариант будет работать нормально для обещаний, созданных исключительно с помощью JavaScript - не зависящих от некоторого асинхронного поведения более низкого уровня - было бы странно, если бы эти обещания имели поведение, отличное от тех, которые предоставляются API. Все они должны использовать одинаковое поведение, когда дело доходит до then
выполнения обратного вызова и использования очереди заданий.