Dlaczego obietnice javascript ES6 są kontynuowane po rozwiązaniu?

104

Jak rozumiem, obietnica jest czymś, co może rozwiązać () lub odrzucić (), ale byłem zaskoczony, gdy dowiedziałem się, że kod w obietnicy nadal jest wykonywany po wywołaniu rozwiązania lub odrzucenia.

Rozważałem rozwiązanie lub odrzucenie bycia przyjazną asynchronicznie wersją wyjścia lub powrotu, która zatrzymałaby wszystkie natychmiastowe wykonywanie funkcji.

Czy ktoś może wyjaśnić, dlaczego poniższy przykład czasami pokazuje plik console.log po wywołaniu rozwiązania:

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
źródło
13
Rozsądne pytanie, ale z drugiej strony JS wykonuje po prostu jedną instrukcję po drugiej, tak jak mu każesz. resolve()nie jest instrukcją sterującą JS, która w magiczny sposób miałaby wpływ return, to tylko wywołanie funkcji i tak, wykonanie jest kontynuowane po nim.
To dobre pytanie i nawet po przeczytaniu wszystkich odpowiedzi nie jestem pewien co do najlepszych praktyk ...
Gabriel Glenn
Myślę, że to nieporozumienie bierze się z tego, co dokładnie kończysz za pomocą resell (): obietnica JEST rozwiązana zaraz po wywołaniu resell (), ale jak już powiedzieli inni, nie oznacza to, że funkcja, która wypowiedziała obietnicę, zakończyła swoją obowiązek także, więc trwa aż do „normalnego” zakończenia.
Giuseppe Bertone

Odpowiedzi:

151

JavaScript ma koncepcję „biegnij do końca” . O ile nie zostanie zgłoszony błąd, funkcja jest wykonywana do momentu osiągnięcia returninstrukcji lub jej końca. Inny kod poza funkcją nie może w to przeszkadzać (chyba że ponownie zostanie zgłoszony błąd).

Jeśli chcesz resolve()wyjść z funkcji inicjalizatora, musisz ją poprzedzić return:

return new Promise(function(resolve, reject) {
    return resolve();
    console.log("Not doing more stuff after a return statement");
});
Felix Kling
źródło
Cześć Felix - myślę, że to tylko część historii - druga część resolve()jest sama w sobie funkcją asynchroniczną. Jak widzieliśmy w drugiej (usuniętej) odpowiedzi, niektórzy uważają, że wywołanie resolvenatychmiast uruchomi wszelkie wywołania zwrotne.
Alnitak
3
resolveSam @Alnitak nie jest asynchroniczny, jest całkowicie synchroniczny. Chociaż używa się ściśle ES6 API, nie można zaobserwować, czy jest synchroniczne, czy asynchroniczne.
Esailija,
2
@Esailija ok, być może nie było jasne. Niektórzy uważają, że wywołanie resolvespowoduje natychmiastowe wywołanie wszystkich zarejestrowanych wywołań zwrotnych, tak że są one częścią bieżącego stosu wywołań. To nieprawda, zamiast tego po prostu kolejkuje wywołania zwrotne (i masz rację, nie jest asynchroniczny, ale po prostu robi swoje i natychmiast się kończy)
Alnitak
@Alnitak: Rozumiem, co mówisz. Po prostu zinterpretowałem to jako, dlaczego console.logpojawia się zamiast, zamiast dlaczego pojawia się w tej kolejności. Jak dotąd to, co resolvespełnia i jak obiecuje, nie ma znaczenia dla tego, jak interpretuję pytanie. Ale oczywiście nadal ważne jest, aby wiedzieć w kontekście obietnic. Jednym z powodów, dla których zagłosowałem za twoją odpowiedzią :)
Felix Kling
9
@Bergi, w swoim redagowaniu mówisz „return solution ();” co wydaje się niezwykłe. Aby przekonać się, że nie dzieje się w tym nic ważnego, musiałem przeczytać dokumentację i zobaczyć, że (1) metoda solution () nie wydaje się zwracać żadnych konsekwencji, a (2) wartość zwracana przez wywołanie zwrotne inicjalizacji nie wydają się być używane. Czy nie byłoby jaśniej powiedzieć „rozwiązać (); powrót”; unikając w ten sposób tego rozproszenia?
Don Hatch
19

Wywołania zwrotne, które będą wywoływane podczas resolveobietnicy, nadal wymagają, aby specyfikacja była wywoływana asynchronicznie. Ma to na celu zapewnienie spójnego zachowania podczas korzystania z obietnic dla kombinacji działań synchronicznych i asynchronicznych.

Dlatego po wywołaniu resolvewywołanie zwrotne jest umieszczane w kolejce , a wykonywanie funkcji jest kontynuowane natychmiast z dowolnym kodem następującym po resolve()wywołaniu.

Dopiero gdy pętla zdarzeń JS odzyska kontrolę, wywołanie zwrotne może zostać usunięte z kolejki i faktycznie wywołane.

Alnitak
źródło
1
Kolejkowanie wywołań zwrotnych jest udokumentowane w specyfikacji A + czy w ES6?
thefourtheye
5
@thefourtheye: specyfikacja pętli zdarzeń jest obecnie częścią HTML5 . ES6 definiuje wywoływaną wewnętrzną metodę EnqueueJob, która jest wywoływana przez .then.
Felix Kling
@thefourtheye: Właściwie ES6 również wydaje się definiować kolejki: people.mozilla.org/~jorendorff/… . Wydaje mi się, że jest to związane z pętlą zdarzeń w jedną lub drugą stronę.
Felix Kling
@FelixKling dzięki za linki - wiedziałem, że tak to działa, ale nie mogłem zacytować rozdziału i wersetu
Alnitak
2
@FelixKling to mikrozadania / makrozadania, tutaj jest część specyfikacji, która "odracza" "Kiedy nie ma uruchomionego kontekstu wykonania, a stos kontekstów wykonania jest pusty, implementacja ECMAScript usuwa pierwsze PendingJob z kolejki zadań i wykorzystuje zawarte w nim informacje w nim, aby utworzyć kontekst wykonania i rozpocząć wykonywanie skojarzonej operacji abstrakcyjnej zadania. "
Benjamin Gruenbaum