Почему Javascript ES6 Promises продолжает выполнение после разрешения?

104

Насколько я понимаю, обещание - это то, что может разрешить () или отклонить (), но я был удивлен, обнаружив, что код в обещании продолжает выполняться после вызова решения или отклонения.

Я считал, что функция resolve или reject является асинхронной версией exit или return, которая остановит все немедленное выполнение функций.

Может ли кто-нибудь объяснить, почему в следующем примере иногда отображается console.log после вызова разрешения:

var call = function() {
    return new Promise(function(resolve, reject) {
        resolve();
        console.log("Doing more stuff, should not be visible after a resolve!");
    });
};

call().then(function() {
    console.log("resolved");
});

jsbin

Людвиг ван Бетховен
источник
13
Разумный вопрос, но опять же, JS просто выполняет одну инструкцию за другой, как вы ему говорите. resolve()не является оператором управления JS, который волшебным образом мог бы иметь эффект return, это просто вызов функции, и да, выполнение продолжается после него.
Это хороший вопрос, и даже после прочтения всех ответов я не уверен в лучших практиках ...
Габриэль Гленн
Я думаю, что недоразумение происходит из-за того, что именно вы завершаете с помощью resolve (): обещание IS разрешается сразу после того, как вы вызываете resolve (), но, как уже говорили другие, это не означает, что функция, завершившая обещание, завершила его обязанности тоже, поэтому он продолжается, пока не достигнет «нормального» завершения.
Джузеппе Бертоне

Ответы:

152

В JavaScript есть концепция «выполнить до завершения» . Если не возникает ошибка, функция выполняется до тех пор, пока не будет достигнут returnоператор или его конец. Другой код вне функции не может этому помешать (если, опять же, не возникнет ошибка).

Если вы хотите resolve()выйти из функции инициализатора, вы должны добавить ее return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Феликс Клинг
источник
Привет, Феликс, я думаю, что это только часть истории, другая часть resolve()- это асинхронная функция. Как мы видели в другом (удаленном) ответе, некоторые люди считают, что при вызове resolveнемедленно запускаются любые обратные вызовы.
Alnitak
3
resolveСам @Alnitak не асинхронен, а полностью синхронен. Несмотря на то, что используется строго ES6 API, невозможно наблюдать, синхронный он или асинхронный.
Esailija 06
2
@Esailija, ладно, возможно, я не понял. Некоторые люди считают, что вызов resolveприведет к немедленному вызову любых зарегистрированных обратных вызовов, так что они будут частью текущего стека вызовов. Это неправда, вместо этого он просто ставит в очередь обратные вызовы (и вы правы, это не асинхронно, но он просто выполняет свою задачу и немедленно завершает работу)
Alnitak
@Alnitak: Я понимаю, о чем вы говорите. Я просто интерпретировал это как «почему» console.logвместо «почему» появляется в таком порядке. Пока что то, что resolveделает и как обещает, не имеет отношения к тому, как я интерпретирую вопрос. Но, конечно, это все же важно знать в контексте обещаний. Одна из причин, по которой я проголосовал за ваш ответ :)
Феликс Клинг
9
@Bergi, при редактировании вы говорите "return resolve ();" что кажется необычным. Чтобы убедить себя, что там ничего важного не происходит, мне пришлось прочитать документацию и увидеть, что (1) resolve (), похоже, не возвращает ничего существенного, и (2) возвращаемое значение обратного вызова инициализации не похоже, используются. Разве не было бы яснее сказать «resolve (); return;» тем самым избегая этого отвлечения?
Дон Хэтч
19

Обратные вызовы, которые будут вызываться при resolveвыполнении обещания, по-прежнему должны вызываться в соответствии со спецификацией асинхронно. Это необходимо для обеспечения согласованного поведения при использовании обещаний для сочетания синхронных и асинхронных действий.

Поэтому, когда вы вызываете resolveобратный вызов, он ставится в очередь , и выполнение функции немедленно продолжается с любым кодом, следующим за resolve()вызовом.

Только после того, как цикл событий JS будет возвращен в управление, обратный вызов может быть удален из очереди и фактически вызван.

Альнитак
источник
1
Очередь обратного вызова задокументирована в спецификациях A + или в ES6?
thefourtheye 06
5
@thefourtheye: Спецификация цикла событий теперь фактически является частью HTML5 . ES6 определяет внутренний метод EnqueueJob, который вызывается .then.
Феликс Клинг,
@thefourtheye: На самом деле, похоже, что ES6 также определяет очереди: people.mozilla.org/~jorendorff/… . Я предполагаю, что это так или иначе связано с циклом событий.
Феликс Клинг,
@FelixKling, спасибо за ссылки - я знал, как это работает, но не мог процитировать главу и стих
Alnitak
2
@FelixKling это микрозадачи / макрозадачи, вот часть спецификации, которая «откладывает»: «Когда нет текущего контекста выполнения и стек контекста выполнения пуст, реализация ECMAScript удаляет первое PendingJob из очереди заданий и использует содержащуюся информацию. в нем для создания контекста выполнения и запускает выполнение связанной абстрактной операции Job. "
Бенджамин Грюнбаум