Czy jest jakaś różnica między:
const [result1, result2] = await Promise.all([task1(), task2()]);
i
const t1 = task1();
const t2 = task2();
const result1 = await t1;
const result2 = await t2;
i
const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
javascript
async-await
Ukryty
źródło
źródło
Odpowiedzi:
Na potrzeby tej odpowiedzi posłużę się kilkoma przykładowymi metodami:
res(ms)
jest funkcją, która przyjmuje liczbę całkowitą z milisekund i zwraca obietnicę, która jest rozpatrywana po tych wielu milisekundach.rej(ms)
jest funkcją, która przyjmuje liczbę całkowitą z milisekund i zwraca obietnicę, która odrzuca po tych wielu milisekundach.Połączenie
Przykład 1res
uruchamia stoper. KorzystaniePromise.all
z czekania na kilka opóźnień ustąpi po zakończeniu wszystkich opóźnień, ale pamiętaj, że są one wykonywane w tym samym czasie:Pokaż fragment kodu
Oznacza to, że
Promise.all
problem zostanie rozwiązany z danymi z wewnętrznych obietnic po 3 sekundach.Ale
Przykład nr 2Promise.all
ma zachowanie „szybkie niepowodzenie” :Pokaż fragment kodu
Jeśli
Przykład nr 3async-await
zamiast tego użyjesz , będziesz musiał poczekać, aż każda obietnica zostanie rozwiązana sekwencyjnie, co może nie być tak wydajne:Pokaż fragment kodu
źródło
unhandledrejection
błędy. Nigdy nie będziesz chciał tego używać. Dodaj to do swojej odpowiedzi.Pierwsza różnica - porażka szybko
Zgadzam się z odpowiedzią @ zzzzBov, ale przewaga Promise.all „szybko zawiedzie” to nie tylko jedna różnica. Niektórzy użytkownicy w komentarzach pytają, dlaczego używać Promise.all, gdy jest to szybsze tylko w negatywnym scenariuszu (gdy niektóre zadanie nie powiedzie się). I pytam dlaczego nie? Jeśli mam dwa niezależne, równoległe zadania asynchroniczne, a pierwsze z nich jest rozwiązywane w bardzo długim czasie, ale drugie jest odrzucane w bardzo krótkim czasie, dlaczego użytkownik ma czekać na komunikat o błędzie „bardzo długi czas” zamiast „bardzo krótki czas”? W rzeczywistych zastosowaniach musimy wziąć pod uwagę negatywny scenariusz. Ale OK - w tej pierwszej różnicy możesz zdecydować, która alternatywa dla Promise.all czy wielokrotnego czekania.
Druga różnica - obsługa błędów
Ale rozważając obsługę błędów, MUSISZ użyć Promise.all. Nie jest możliwe poprawne obsługiwanie błędów asynchronicznych zadań równoległych wyzwalanych z wieloma oczekiwaniami. W negatywnym scenariuszu zawsze skończysz
UnhandledPromiseRejectionWarning
iPromiseRejectionHandledWarning
chociaż używasz try / catch wszędzie. Dlatego powstał Promise.all. Oczywiście ktoś mógłby powiedzieć, że możemy stłumić te błędy używającprocess.on('unhandledRejection', err => {})
iprocess.on('rejectionHandled', err => {})
ale to nie jest dobra praktyka. Znalazłem wiele przykładów w Internecie, które nie uwzględniają obsługi błędów dla dwóch lub więcej niezależnych równoległych zadań asynchronicznych w ogóle lub rozważają to w niewłaściwy sposób - po prostu używając try / catch i mając nadzieję, że wykryje błędy. Znalezienie dobrej praktyki jest prawie niemożliwe. Dlatego piszę tę odpowiedź.Podsumowanie
Nigdy nie używaj wielokrotnego oczekiwania dla dwóch lub więcej niezależnych równoległych zadań asynchronicznych, ponieważ nie będziesz w stanie poważnie obsłużyć błędów. Zawsze używaj Promise.all () w tym przypadku użycia. Async / await nie zastępuje obietnic. To po prostu ładny sposób, jak używać obietnic ... kod asynchroniczny jest napisany w stylu synchronizacji i możemy uniknąć wielu
then
obietnic.Niektórzy mówią, że używając Promise.all () nie możemy obsługiwać błędów zadań osobno, a jedynie błąd z pierwszej odrzuconej obietnicy (tak, niektóre przypadki użycia mogą wymagać oddzielnej obsługi np. Przy logowaniu). To nie jest problem - patrz nagłówek „Dodatek” poniżej.
Przykłady
Rozważ to zadanie asynchroniczne ...
Kiedy uruchamiasz zadania w pozytywnym scenariuszu, nie ma różnicy między Promise.all a Multiple await. Oba przykłady kończą się
Task 1 succeed! Task 2 succeed!
po 5 sekundach.Kiedy pierwsze zadanie zajmuje 10 sekund w scenariuszu pozytywnym, a zadanie sekundowe zajmuje 5 sekund w scenariuszu negatywnym, występują różnice w wyświetlanych błędach.
Powinniśmy już zauważyć, że robimy coś złego, gdy używamy wielu czeków równolegle. Oczywiście, aby uniknąć błędów, powinniśmy sobie z tym poradzić! Spróbujmy...
Jak widać, aby pomyślnie obsłużyć błąd, musimy dodać tylko jeden catch do
run
funkcji, a kod z logiką catch jest w wywołaniu zwrotnym ( styl async ). Nie potrzebujemy obsługi błędów wewnątrzrun
funkcji, ponieważ funkcja asynchroniczna robi to automatycznie - obietnica odrzuceniatask
funkcji powoduje odrzucenierun
funkcji. Aby uniknąć wywołania zwrotnego, możemy użyć stylu synchronizacji (async / await + try / catch),try { await run(); } catch(err) { }
ale w tym przykładzie nie jest to możliwe, ponieważ nie możemy użyćawait
w głównym wątku - można go używać tylko w funkcji async (jest to logiczne, ponieważ nikt nie chce blok główny wątek). Aby sprawdzić, czy obsługa działa w stylu synchronizacji , możemy wywołaćrun
Funkcja innej funkcji asynchronicznej lub użytkowania Iife (bezpośrednio Wykonano wyrażenie funkcyjne)(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
.To tylko jeden poprawny sposób uruchamiania dwóch lub więcej równoległych zadań asynchronicznych i obsługi błędów. Powinieneś unikać poniższych przykładów.
Możemy spróbować obsłużyć powyższy kod na kilka sposobów ...
... nic nie zostało złapane, ponieważ obsługuje kod synchronizacji, ale
run
jest asynchroniczne... Wtf? Najpierw widzimy, że błąd dla zadania 2 nie został obsłużony, a później został przechwycony. Wprowadzające w błąd i wciąż pełne błędów w konsoli. Nie do użytku w ten sposób.
... tak samo jak powyżej. Użytkownik @Qwerty w swojej usuniętej odpowiedzi zapytał o to dziwne zachowanie, które wydaje się być przechwytywane, ale są też nieobsłużone błędy. Łapiemy błąd, ponieważ run () jest odrzucany w linii ze słowem kluczowym await i może zostać przechwycony przy użyciu try / catch podczas wywoływania run (). Otrzymujemy również nieobsłużony błąd, ponieważ wywołujemy funkcję zadania asynchronicznego synchronicznie (bez słowa kluczowego await), a to zadanie jest uruchamiane poza funkcją run () i również kończy się niepowodzeniem na zewnątrz. Jest podobny, gdy nie jesteśmy w stanie obsłużyć błąd przez try / catch podczas wywoływania niektórych funkcji synchronizacji, która część uruchamia kod w setTimeout ...
function test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }
.... „tylko” dwa błędy (brakuje trzeciego), ale nic nie złapano.
Dodawanie (osobno obsługuj błędy zadań, a także błąd pierwszego niepowodzenia)
... zwróć uwagę, że w tym przykładzie użyłem negatywnegoScenario = true dla obu zadań, aby lepiej zademonstrować, co się dzieje (
throw err
służy do wywołania końcowego błędu)źródło
Ogólnie rzecz biorąc,
Promise.all()
równoległe uruchamianie żądań „asynchronicznych”. Używanieawait
może działać równolegle LUB blokować „synchronizację”.Funkcje test1 i test2 poniżej pokazują, jak
await
można uruchomić async lub sync.test3 pokazuje,
Promise.all()
że jest asynchroniczny.jsfiddle z wynikami w czasie - otwórz konsolę przeglądarki, aby zobaczyć wyniki testów
Zachowanie synchronizacji . NIE działa równolegle, trwa ~ 1800 ms :
Zachowanie asynchroniczne . Działa równolegle, trwa ~ 600 ms :
Zachowanie asynchroniczne . Działa równolegle, trwa ~ 600 ms :
TLDR; Jeśli używasz
Promise.all()
, również "szybko zawiedzie" - przestanie działać w momencie pierwszej awarii którejkolwiek z dołączonych funkcji.źródło
Możesz sam sprawdzić.
Na tych skrzypcach przeprowadziłem test, aby zademonstrować blokującą naturę
await
, w przeciwieństwie do tego,Promise.all
który rozpocznie wszystkie obietnice, a gdy jeden czeka, będzie kontynuowany z innymi.źródło
t1 = task1(); t2 = task2()
a późniejszym użyciemawait
dla nich obu,result1 = await t1; result2 = await t2;
jak w jego pytaniu, w przeciwieństwie do tego, co testujesz, czego używaszawait
w pierwotnym połączeniuresult1 = await task1(); result2 = await task2();
. Kod w jego pytaniu uruchamia wszystkie obietnice naraz. Różnica, jak pokazuje odpowiedź, polega na tym, że awarie będą zgłaszane szybciej poPromise.all
drodze.W przypadku await Promise.all ([task1 (), task2 ()]); „zadanie1 ()” i „zadanie2 ()” będą działać równolegle i będą czekać, aż obie obietnice zostaną zakończone (rozwiązane lub odrzucone). Natomiast w przypadku
t2 będzie działać dopiero po zakończeniu wykonywania t1 (rozwiązaniu lub odrzuceniu). Zarówno t1, jak i t2 nie będą przebiegać równolegle.
źródło