Powiedzmy, że mam zestaw Promise
s, które wysyłają żądania sieciowe, z których jeden się nie powiedzie:
// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]
Promise.all(arr)
.then(res => console.log('success', res))
.catch(err => console.log('error', err)) // This is executed
Powiedzmy, że chcę poczekać, aż wszystkie się zakończą, niezależnie od tego, czy coś się nie powiedzie. Może występować błąd sieci dla zasobu, bez którego mogę żyć, ale jeśli mogę go zdobyć, chcę, zanim przejdę dalej. Chcę z wdziękiem radzić sobie z awariami sieci.
Ponieważ Promises.all
nie pozostawia na to miejsca, jaki jest zalecany sposób postępowania bez korzystania z biblioteki obietnic?
javascript
promise
es6-promise
Nathan Hagen
źródło
źródło
allSettled
która zaspokaja swoje potrzeby bez konieczności toczyć własną rękę.Promise.all
odrzuci, gdy tylko jakakolwiek obietnica odrzuci, więc twój proponowany idiom nie gwarantuje, że wszystkie obietnice zostaną spełnione.Odpowiedzi:
Aktualizacja, prawdopodobnie chcesz użyć wbudowanego natywnego
Promise.allSettled
:Zabawnym faktem jest to, że poniższa odpowiedź stanowiła wcześniejszy sposób dodawania tej metody do języka:]
Jasne, potrzebujesz tylko
reflect
:Lub z ES5:
Lub w twoim przykładzie:
źródło
reflect
jest powszechne w informatyce? Czy możesz podać link do miejsca, w którym zostało to wyjaśnione, np. Na wikipedii lub coś w tym stylu. Ciężko szukałem,Promise.all not even first reject
ale nie wiedziałem, aby wyszukać „Reflect”. Czy ES6 powinien mieć coś w styluPromise.reflect
„Promise.all, ale naprawdę wszystko”?Podobna odpowiedź, ale może bardziej idiomatyczna dla ES6:
W zależności od rodzaju (y) wartości zwracane błędy można często odróżnić dość łatwo (np zastosowanie
undefined
do „nie obchodzi”,typeof
dla prostych wartości non-object,result.message
,result.toString().startsWith("Error:")
itd.)źródło
.map(p => p.catch(e => e))
część zamienia wszystkie odrzucenia na wartości rozstrzygnięte, więcPromise.all
nadal czeka, aż wszystko się skończy, czy poszczególne funkcje zostaną rozpatrzone lub odrzucone, niezależnie od tego, jak długo to potrwa. Spróbuj..catch(e => console.log(e));
nigdy nie jest nazywany, ponieważ to nigdy nie zawodzicatch
jest ogólnie dobrą praktyką IMHO .e
i zwraca go jako zwykłą (sukces) wartość. Taki sam jakp.catch(function(e) { return e; })
tylko krótszy.return
jest niejawny.Odpowiedź Benjamina oferuje świetną abstrakcję do rozwiązania tego problemu, ale liczyłem na mniej abstrakcyjne rozwiązanie. Wyraźnym sposobem rozwiązania tego problemu jest po prostu wywołanie
.catch
wewnętrznych obietnic i zwrócenie błędu z ich wywołania zwrotnego.Idąc o krok dalej, możesz napisać ogólną procedurę obsługi połowów, która wygląda następująco:
to możesz zrobić
Problem polega na tym, że przechwycone wartości będą miały inny interfejs niż nieprzechwycone wartości, więc aby to wyczyścić, możesz zrobić coś takiego:
Teraz możesz to zrobić:
Następnie, aby zachować SUCHO, dochodzisz do odpowiedzi Benjamina:
gdzie teraz wygląda
Zaletą drugiego rozwiązania jest to, że jest ono abstrakcyjne i SUCHE. Minusem jest to, że masz więcej kodu i musisz pamiętać, aby odzwierciedlić wszystkie swoje obietnice, aby wszystko było spójne.
Scharakteryzowałbym moje rozwiązanie jako jednoznaczne i KISS, ale rzeczywiście mniej niezawodne. Interfejs nie gwarantuje, że dokładnie wiesz, czy obietnica się powiodła, czy nie.
Na przykład możesz mieć to:
To nie zostanie złapane
a.catch
, więcNie ma sposobu, aby stwierdzić, który był śmiertelny, a który nie. Jeśli to ważne, będziesz chciał wymusić i interfejs, który śledzi, czy się udało, czy nie (co
reflect
robi).Jeśli chcesz po prostu z wdziękiem obsługiwać błędy, możesz traktować je jako niezdefiniowane wartości:
W moim przypadku nie muszę znać błędu ani tego, jak się nie powiódł - dbam tylko o to, czy mam wartość, czy nie. Pozwolę, aby funkcja generująca obietnicę martwiła się rejestrowaniem konkretnego błędu.
W ten sposób reszta aplikacji może zignorować swój błąd, jeśli chce, i traktować go jako niezdefiniowaną wartość, jeśli chce.
Chcę, aby moje funkcje na wysokim poziomie zawiodły bezpiecznie i nie martwiłem się szczegółami, dlaczego zawiodły jego zależności, a także wolę KISS niż OSUSZANIE, gdy muszę dokonać tego kompromisu - i ostatecznie zdecydowałem się nie używać
reflect
.źródło
Promise
s. Chociażreflect
poprawiasz ponowne użycie kodu, ustanawia on również inny poziom abstrakcji. Ponieważ odpowiedź Nathana do tej pory otrzymała jedynie ułamek głosów pozytywnych w porównaniu do twojej, zastanawiam się, czy to wskazuje na problem z jego rozwiązaniem, którego jeszcze nie rozpoznałem.new Promise((res, rej) => res(new Error('Legitimate error'))
nie można odróżnićnew Promise(((res, rej) => rej(new Error('Illegitimate error'))
? Lub dalej, nie będziesz w stanie filtrować wedługx.status
? Dodam ten punkt do mojej odpowiedzi, aby różnica była bardziej wyraźnaPromise.all()
wariancie, a następnie konsumentowi Obietnicy spoczywa obowiązek wiedzieć, że konkretna obietnica nie zostanie odrzucona, ale zostanie odrzucona połknij to błędy. W rzeczywistościreflect()
metodę tę można by uczynić mniej „abstrakcyjną”, a bardziej wyraźną, nazywając jąPromiseEvery(promises).then(...)
. Złożoność powyższej odpowiedzi w porównaniu z odpowiedzią Benjamina powinna wiele powiedzieć o tym rozwiązaniu.Istnieje gotowa propozycja funkcji, która może tego dokonać natywnie, w waniliowym JavaScripcie:
Promise.allSettled
która przeszła do etapu 4, jest oficjalna w ES2020 i jest implementowana we wszystkich nowoczesnych środowiskach . Jest bardzo podobny doreflect
funkcji z tej drugiej odpowiedzi . Oto przykład ze strony propozycji. Wcześniej musiałbyś zrobić:Użycie
Promise.allSettled
zamiast tego powyższego będzie równoznaczne z:Osoby korzystające z nowoczesnych środowisk będą mogły korzystać z tej metody bez żadnych bibliotek . W nich następujący fragment kodu powinien działać bez problemów:
Wynik:
W przypadku starszych przeglądarek dostępny jest tutaj wypełnienie zgodne ze specyfikacją .
źródło
Naprawdę podoba mi się odpowiedź Benjamina i to, jak zasadniczo zamienia wszystkie obietnice w zawsze rozwiązujące, ale czasem z błędem jako rezultatem. :)
Oto moja próba na twoją prośbę na wypadek, gdybyś szukał alternatyw. Ta metoda po prostu traktuje błędy jako prawidłowe wyniki i jest kodowana podobnie do
Promise.all
innych:źródło
settle
. Mamy to również w bluebird, lubię lepiej odzwierciedlać, ale jest to realne rozwiązanie, gdy masz to dla tablicy.Promise
poprawnie używać konstruktora (i unikać tegovar resolve
)?Promise.all
Połknie każdą odrzuconą obietnicy i przechowywanie błędu w zmiennej, tak powróci, gdy wszystkie obietnice zostały rozwiązane. Następnie możesz ponownie wyrzucić błąd lub zrobić cokolwiek innego. W ten sposób wydaje mi się, że wyszedłbyś z ostatniego odrzucenia zamiast pierwszego.źródło
err.push(error)
, dzięki czemu wszystkie błędy mogą zostać wyrzucone z bufora.Miałem ten sam problem i rozwiązałem go w następujący sposób:
W takim przypadku
Promise.all
będzie czekać na wejścieresolved
lubrejected
stan każdej Obietnicy .Dzięki temu rozwiązaniu „zatrzymujemy
catch
wykonywanie” w sposób nieblokujący. W rzeczywistości niczego nie zatrzymujemy, po prostu wracamyPromise
do stanu oczekującego, który zwraca inny,Promise
gdy zostanie rozwiązany po upływie limitu czasu.źródło
Promise.all
. Szukam sposobu, aby słuchać, kiedy wszystkie obietnice zostały przywołane, ale nie przywołuję ich osobiście. Dzięki.all()
robi to, czeka na spełnienie wszystkich obietnic lub odrzucenie co najmniej jednej z nich..all
wszystko rozpala.then
lub.all
połączenie), ale działają po utworzeniu.Powinno to być spójne z tym, jak to robi Q :
źródło
Odpowiedź Benjamina Gruenbauma jest oczywiście świetna. Ale widzę też, że punkt widzenia Nathana Hagena z poziomem abstrakcji wydaje się niejasny. Posiadanie krótkich właściwości obiektu, takich jak
e & v
, również nie pomaga, ale oczywiście można to zmienić.W JavaScript nie jest standard obiektu Error o nazwie
Error
,. Idealnie zawsze rzucasz instancją / potomkiem tego. Zaletą jest to, że możesz to zrobićinstanceof Error
i wiesz, że coś jest błędem.Korzystając z tego pomysłu, oto moje podejście do problemu.
Zasadniczo złap błąd, jeśli błąd nie jest typu Błąd, zawiń błąd w obiekcie Error. Wynikowa tablica będzie miała albo rozstrzygnięte wartości, albo Obiekty błędów, które można sprawdzić.
Instancja wewnątrz haczyka jest na wypadek, gdybyś użył biblioteki zewnętrznej, która może
reject("error")
, zamiastreject(new Error("error"))
.Oczywiście możesz mieć obietnice, jeśli rozwiążesz błąd, ale w takim przypadku najprawdopodobniej i tak byłoby traktowane jako błąd, jak pokazuje ostatni przykład.
Inną zaletą robienia tego jest niszczenie tablic, które jest proste.
Zamiast
Można argumentować, że
!error1
sprawdzenie jest prostsze niż instanceof, ale musisz zniszczyć obav & e
.źródło
Zamiast odrzucać, rozwiąż go za pomocą obiektu. Możesz zrobić coś takiego podczas realizacji obietnicy
źródło
Myślę następujące oferty nieco inne podejście ... porównać
fn_fast_fail()
zfn_slow_fail()
... choć ten ostatni nie zawieść jako takie ... można sprawdzić, czy jeden lub obaa
ib
jest instancjąError
ithrow
żeError
jeśli chcesz, aby osiągnąćcatch
blokowe (na przykładif (b instanceof Error) { throw b; }
). Zobacz jsfiddle .źródło
Oto mój zwyczaj
settledPromiseAll()
W porównaniu do
Promise.all
Jeśli wszystkie obietnice zostaną rozwiązane, działa dokładnie tak samo jak standardowe.
Jeśli jedna lub więcej obietnic zostanie odrzuconych, zwraca pierwszą odrzuconą podobnie jak standardowa, ale w przeciwieństwie do oczekiwania na wszystkie obietnice, które zostaną rozpatrzone / odrzucone.
Dla odważnych możemy zmienić
Promise.all()
:OSTROŻNY . Zasadniczo nigdy nie zmieniamy wbudowanych, ponieważ może to zepsuć inne niepowiązane biblioteki JS lub kolidować z przyszłymi zmianami standardów JS.
Mój
settledPromiseall
jest wstecznie kompatybilny zPromise.all
i rozszerza swoją funkcjonalność.Ludzie, którzy opracowują standardy - dlaczego nie dołączyć do nowego standardu Promise?
źródło
Promise.all
z zastosowaniem nowoczesnegoasync/await
podejściaźródło
Chciałbym zrobić:
źródło
Możesz wykonywać swoją logikę sekwencyjnie za pomocą synchronicznego executora nsynjs . Zatrzymuje się przy każdej obietnicy, czeka na rozstrzygnięcie / odrzucenie i przypisuje wynik rozstrzygania do
data
właściwości lub zgłasza wyjątek (do obsługi, że będziesz potrzebował zablokować try / catch). Oto przykład:źródło
Używam następujących kodów od ES5.
Podpis użytkowania jest taki sam
Promise.all
. Główną różnicą jest to, żePromise.wait
będą czekać na wszystkie obietnice zakończenia pracy.źródło
Wiem, że to pytanie ma wiele odpowiedzi i jestem pewien, że muszą (jeśli nie wszystkie) być poprawne. Jednak bardzo trudno było mi zrozumieć logikę / przepływ tych odpowiedzi.
Spojrzałem więc na Pierwotną Implementację
Promise.all()
i próbowałem naśladować tę logikę - z wyjątkiem tego, że nie zatrzymam wykonania, jeśli jedna Obietnica się nie powiedzie.Objaśnienie:
- Zapętlić dane wejściowe
promisesList
i wykonać każdą obietnicę.- Bez względu na to, czy obietnica została rozwiązana, czy odrzucona: zapisz wynik obietnicy w
result
tablicy zgodnie zindex
. Zapisz także status rozwiązania / odrzucenia (isSuccess
).- Po zakończeniu wszystkich obietnic, zwróć jedną obietnicę z wynikiem wszystkich pozostałych.
Przykład zastosowania:
źródło
Promise.all
, zbyt wiele rzeczy pójdzie nie tak. Twoja wersja nie obsługuje na przykład pustych danych wejściowych.Nie wiem, jakiej biblioteki obietnic używasz, ale większość ma coś takiego jak allSettled .
Edycja: Ok, ponieważ chcesz używać zwykłego ES6 bez bibliotek zewnętrznych, nie ma takiej metody.
Innymi słowy: musisz ręcznie zapętlić swoje obietnice i rozwiązać nową połączoną obietnicę, gdy tylko wszystkie obietnice zostaną rozliczone.
źródło