Por que o javascript ES6 Promises continua em execução após uma resolução?

97

Pelo que entendi, uma promessa é algo que pode resolver () ou rejeitar (), mas fiquei surpreso ao descobrir que o código na promessa continua a ser executado depois que uma resolução ou rejeição é chamada.

Considerei resolver ou rejeitar uma versão assíncrona de saída ou retorno, que interromperia toda a execução imediata da função.

Alguém pode explicar por que o exemplo a seguir às vezes mostra o console.log após uma chamada de resolução:

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

Ludwig Van Beethoven
fonte
12
Pergunta razoável, mas, novamente, JS apenas executa uma instrução após a outra, como você mandar. resolve()não é uma instrução de controle JS que magicamente teria o efeito de return, é apenas uma chamada de função e, sim, a execução continua depois dela.
Esta é uma boa pergunta, e mesmo depois de ler todas as respostas, não tenho certeza sobre as melhores práticas ...
Gabriel Glenn
Acho que o mal-entendido vem do que exatamente você está encerrando com resolve (): a promessa É resolvida logo após você chamar resolve (), mas como já foi dito por outros, isso não significa que a função que encerrou a promessa havia encerrado sua dever também, por isso continua até atingir uma rescisão "normal".
Giuseppe Bertone

Respostas:

143

JavaScript tem o conceito de "execução completa" . A menos que um erro seja lançado, uma função é executada até que uma returninstrução ou seu fim seja alcançado. Outro código fora da função não pode interferir nisso (a menos que, novamente, um erro seja lançado).

Se você quiser resolve()sair da função inicializador, terá que acrescentá-la return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Felix Kling
fonte
Oi Felix - acho que isso é apenas parte da história - a outra parte é que resolve()é uma função assíncrona. Como vimos na outra resposta (excluída), algumas pessoas acreditam que a chamada resolvegerará retornos de chamada imediatamente.
Alnitak
3
O resolvepróprio @Alnitak não é assíncrono, é totalmente síncrono. Apesar de usar estritamente a API ES6, não é observável se é síncrono ou assíncrono.
Esailija
1
@Esailija ok, talvez eu não tenha entendido. Algumas pessoas acreditam que a chamada resolveresultará em quaisquer retornos de chamada registrados sendo imediatamente invocados de forma que façam parte da pilha de chamadas atual. Isso não é verdade, em vez disso, ele apenas enfileira os retornos de chamada (e você está certo, não é assíncrono, mas apenas faz o seu trabalho e termina imediatamente)
Alnitak
@Alnitak: Eu entendo o que você está dizendo. Eu apenas interpretei como por que console.logaparece às em vez de por que aparece nessa ordem. Até agora, o que resolvefaz e como promete é irrelevante para a forma como interpreto a pergunta. Mas é claro que ainda é importante saber no contexto das promessas. Um dos motivos pelos quais votei positivamente em sua resposta :)
Felix Kling
9
@Bergi, em sua edição, você diz "return resolve ();" o que parece incomum. Para me convencer de que não há nada de importante acontecendo lá, tive que ler a documentação e ver que (1) resolve () não parece retornar nada de importante e (2) o valor de retorno do callback de inicialização não parecem ser usados. Não seria mais claro dizer "resolve (); return;" evitando assim essa distração?
Don Hatch
19

Os retornos de chamada que serão invocados quando você resolveuma promessa ainda são exigidos pela especificação para serem chamados de forma assíncrona. Isso é para garantir um comportamento consistente ao usar promessas para uma combinação de ações síncronas e assíncronas.

Portanto, quando você invoca, resolveo retorno de chamada é enfileirado e a execução da função continua imediatamente com qualquer código após a resolve()chamada.

Somente quando o loop de evento JS recebe o controle de volta, o retorno de chamada pode ser removido da fila e realmente invocado.

Alnitak
fonte
1
O enfileiramento de retorno de chamada está documentado nas especificações A + ou no ES6?
thefourtheye
5
@thefourtheye: A especificação do loop de evento agora faz parte do HTML5 . ES6 define um método interno chamado EnqueueJob, que é invocado por .then.
Felix Kling
@thefourtheye: Na verdade, ES6 também parece definir filas: people.mozilla.org/~jorendorff/… . Eu acho que está relacionado ao loop de eventos de uma forma ou de outra.
Felix Kling
@FelixKling obrigado pelos links - Eu sabia que era assim que funcionava, mas não conseguia citar capítulo e versículo
Alnitak
2
@FelixKling são microtarefas / macrotarefas, aqui está a parte da especificação que "adia" "Quando não há contexto de execução em execução e a pilha de contexto de execução está vazia, a implementação ECMAScript remove o primeiro PendingJob de uma fila de tarefas e usa as informações contidas nele para criar um contexto de execução e iniciar a execução da operação abstrata Job associada. "
Benjamin Gruenbaum