Perché javascript ES6 Promises continua l'esecuzione dopo una risoluzione?


97

Da quanto ho capito, una promessa è qualcosa che può risolvere () o rifiutare (), ma sono rimasto sorpreso di scoprire che il codice nella promessa continua ad essere eseguito dopo che è stata chiamata una risoluzione o un rifiuto.

Ho considerato la risoluzione o il rifiuto di essere una versione asincrona di uscita o ritorno, che interromperebbe l'esecuzione immediata di tutte le funzioni.

Qualcuno può spiegare il pensiero alla base del motivo per cui il seguente esempio a volte mostra console.log dopo una chiamata di risoluzione:

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


12
Domanda ragionevole, ma poi di nuovo, JS esegue solo un'istruzione dopo l'altra come le dici tu. resolve()non è un'istruzione di controllo JS che magicamente avrebbe l'effetto di return, è solo una chiamata di funzione e sì, l'esecuzione continua dopo di essa.

Questa è una buona domanda, e anche dopo aver letto tutte le risposte, non sono sicuro delle migliori pratiche ...
Gabriel Glenn

Penso che il malinteso derivi da ciò che esattamente stai terminando con resolver (): la promessa È risolta subito dopo aver chiamato risoluzioni (), ma come già detto da altri, questo non significa che la funzione che ha terminato la promessa abbia terminato la sua dovere anche, quindi continua fino a quando non raggiunge una risoluzione "normale".
Giuseppe Bertone

Risposte:


143

JavaScript ha il concetto di "esecuzione fino al completamento" . A meno che non venga generato un errore, una funzione viene eseguita finché returnnon viene raggiunta un'istruzione o la sua fine. Altro codice al di fuori della funzione non può interferire con quello (a meno che, di nuovo, non venga generato un errore).

Se vuoi resolve()uscire dalla tua funzione di inizializzazione, devi anteporlo con return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});

Ciao Felix, penso che questa sia solo una parte della storia, l'altra parte è che resolve()è essa stessa una funzione asincrona. Come abbiamo visto nell'altra risposta (cancellata), alcune persone credono che la chiamata resolveeseguirà immediatamente qualsiasi callback.
Alnitak

3
@Alnitak in resolvesé non è asincrono, è completamente sincrono. Sebbene utilizzi rigorosamente l'API ES6 non è osservabile se è sincrono o asincrono.
Esailija

1
@Esailija ok, forse non ero chiaro. Alcune persone credono che la chiamata resolvecomporterà il richiamo immediato di qualsiasi callback registrato in modo che faccia parte dello stack di chiamate corrente. Non è vero, invece mette solo in coda i callback (e hai ragione, non è asincrono, ma fa semplicemente il suo dovere e termina immediatamente)
Alnitak

@ Alnitak: capisco cosa stai dicendo. L'ho semplicemente interpretato come il motivo per cui viene console.logvisualizzato invece del perché viene visualizzato in quell'ordine. Finora, cosa resolvefa e come promette è irrilevante per come interpreto la domanda. Ma ovviamente è ancora importante sapere nel contesto delle promesse. Uno dei motivi per cui ho votato positivamente la tua risposta :)
Felix Kling

9
@Bergi, nella tua modifica, dici "returnublesho ();" il che sembra insolito. Per convincermi che non c'è niente di importante in corso lì, ho dovuto leggere la documentazione e vedere che (1) resolver () non sembra restituire nulla di conseguenza, e (2) il valore di ritorno del callback di inizializzazione no sembrano essere utilizzati. Non sarebbe più chiaro dire "resolved (); return;" evitando così questa distrazione?
Don Hatch

19

I callback che verranno richiamati quando resolveuna promessa è ancora richiesta dalla specifica per essere chiamati in modo asincrono. Questo per garantire un comportamento coerente quando si utilizzano promesse per una combinazione di azioni sincrone e asincrone.

Pertanto, quando si richiama resolvela richiamata viene messa in coda e l'esecuzione della funzione continua immediatamente con qualsiasi codice successivo alla resolve()chiamata.

Solo una volta che il ciclo di eventi JS viene restituito al controllo, la richiamata può essere rimossa dalla coda e effettivamente richiamata.


1
L'accodamento della richiamata è documentato nelle specifiche A + o in ES6?
quarto giorno

5
@thefourtheye: la specifica del ciclo di eventi è attualmente parte di HTML5 . ES6 definisce un metodo interno chiamato EnqueueJob, che viene richiamato da .then.
Felix Kling

@thefourtheye: In realtà, anche ES6 sembra definire le code: people.mozilla.org/~jorendorff/… . Immagino che sia correlato al ciclo di eventi in un modo o nell'altro.
Felix Kling

@FelixKling grazie per i collegamenti - Sapevo che funzionava così, ma non potevo citare capitolo e versetto
Alnitak

2
@FelixKling è microtasks / macrotasks, ecco la parte nella specifica che "rinvia" "Quando non c'è un contesto di esecuzione in esecuzione e lo stack del contesto di esecuzione è vuoto, l'implementazione ECMAScript rimuove il primo PendingJob da una coda di lavori e utilizza le informazioni contenute in esso per creare un contesto di esecuzione e avvia l'esecuzione dell'operazione astratta del lavoro associata. "
Benjamin Gruenbaum
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.