Przekształciłem swój kod w obietnice i zbudowałem wspaniały długi, płaski łańcuch obietnic , składający się z wielu .then()
wywołań zwrotnych. Na koniec chcę zwrócić pewną wartość złożoną i muszę uzyskać dostęp do wielu wyników pośrednich obietnic . Jednak wartości rozdzielczości od połowy sekwencji nie są objęte zakresem ostatniego oddzwaniania, jak uzyskać do nich dostęp?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
javascript
, ma znaczenie w innym języku. Po prostu używam odpowiedzi „przerwać łańcuch” w java i jdeferredOdpowiedzi:
Zerwać łańcuch
Kiedy potrzebujesz dostępu do wartości pośrednich w swoim łańcuchu, powinieneś podzielić łańcuch na te pojedyncze części, których potrzebujesz. Zamiast dołączać jedno wywołanie zwrotne i próbować wielokrotnie użyć jego parametru, dołącz wiele wywołań zwrotnych do tej samej obietnicy - wszędzie tam, gdzie potrzebujesz wartości wynikowej. Nie zapominaj, że obietnica reprezentuje (przybliża) przyszłą wartość ! Oprócz uzyskiwania jednej obietnicy od drugiej w liniowym łańcuchu, użyj kombinacji obietnic, które zostały ci przekazane przez twoją bibliotekę, aby zbudować wartość wynikową.
Spowoduje to bardzo prosty przepływ sterowania, przejrzysty zestaw funkcji, a tym samym łatwą modularyzację.
Zamiast destructuring parametrów w zwrotnego po
Promise.all
które stały się dostępne tylko z ES6 w ES5then
wezwanie zostanie zastąpiony przez fajną metody pomocnika, który został przewidziany przez wiele bibliotek obietnica ( Q , Bluebird , gdy ...).spread(function(resultA, resultB) { …
.Bluebird posiada również specjalną
join
funkcję, która zastępuje tę kombinacjęPromise.all
+spread
prostszą (i bardziej wydajną) konstrukcją:źródło
promiseA
ipromiseB
są tutaj funkcje (zwracające obietnicę).spread
była bardzo przydatna w tym wzorze. Aby uzyskać bardziej nowoczesne rozwiązania, zobacz przyjętą odpowiedź. Jednak już zaktualizowałem odpowiedź z jawnym przekazaniem i nie ma naprawdę żadnego powodu, aby nie aktualizować również tej.ECMAScript Harmony
Oczywiście ten problem został również rozpoznany przez projektantów języka. Wykonali dużo pracy, a propozycja funkcji asynchronicznych w końcu się weszła
ECMAScript 8
Nie potrzebujesz już pojedynczej funkcji
then
wywołania ani wywołania zwrotnego, ponieważ w funkcji asynchronicznej (która zwraca obietnicę po wywołaniu) możesz po prostu poczekać, aż obietnice zostaną rozwiązane bezpośrednio. Zawiera również dowolne struktury kontrolne, takie jak warunki, pętle i klauzule try-catch, ale dla wygody nie potrzebujemy ich tutaj:ECMAScript 6
Podczas gdy czekaliśmy na ES8, użyliśmy już bardzo podobnej składni. ES6 jest wyposażony w funkcje generatora , które pozwalają rozbić wykonanie na części przy arbitralnie umieszczonych
yield
słowach kluczowych. Te plastry można uruchamiać jeden po drugim, niezależnie, a nawet asynchronicznie - i właśnie to robimy, gdy chcemy poczekać na rozwiązanie obietnicy przed uruchomieniem następnego kroku.Istnieją dedykowane biblioteki (takie jak co lub task.js ), ale także wiele bibliotek obiecujących ma funkcje pomocnicze ( Q , Bluebird , kiedy ,…), które wykonują to asynchroniczne wykonywanie krok po kroku , gdy dajesz im funkcję generatora, która daje obietnice.
Działało to w Node.js od wersji 4.0, również kilka przeglądarek (lub ich wersji deweloperskich) stosunkowo wcześnie obsługiwało składnię generatora.
ECMAScript 5
Jednak jeśli chcesz / musisz być kompatybilny wstecz, nie możesz używać tych bez transpilatora. Zarówno funkcje generatora, jak i funkcje asynchroniczne są obsługiwane przez bieżące oprzyrządowanie, patrz na przykład dokumentacja Babel na temat generatorów i funkcji asynchronicznych .
Istnieje również wiele innych języków kompilacji do JS, które są przeznaczone do ułatwienia programowania asynchronicznego. Zwykle używają składni podobnej do
await
(np. Iced CoffeeScript ), ale są też inne, które zawierajądo
adnotacje podobne do Haskella (np. LatteJs , monadic , PureScript lub LispyScript ).źródło
getExample
nadal jest funkcją, która zwraca obietnicę, działa podobnie jak funkcje w innych odpowiedziach, ale ma ładniejszą składnię. Możeszawait
wywołać innąasync
funkcję lub powiązać.then()
jej wynik.steps.next().value.then(steps.next)...
ale to nie działało.Kontrola synchroniczna
Przypisywanie obietnic na później potrzebne wartości do zmiennych, a następnie uzyskiwanie ich wartości poprzez kontrolę synchroniczną. W przykładzie użyto
.value()
metody bluebird, ale wiele bibliotek zapewnia podobną metodę.Można tego użyć do dowolnej liczby wartości:
źródło
Zagnieżdżanie (i) zamknięć
Używanie zamknięć do utrzymania zakresu zmiennych (w naszym przypadku parametry funkcji wywołania zwrotnego sukcesu) jest naturalnym rozwiązaniem JavaScript. Dzięki obietnicom możemy dowolnie zagnieżdżać i spłaszczać
.then()
wywołania zwrotne - są one semantycznie równoważne, z wyjątkiem zakresu wewnętrznego.Oczywiście buduje to piramidę wcięcia. Jeśli wcięcie staje się zbyt duże, nadal możesz zastosować stare narzędzia do przeciwdziałania piramidie losu : zmodularyzuj, użyj dodatkowych nazwanych funkcji i spłaszcz łańcuch obietnic, gdy tylko nie będziesz już potrzebować zmiennej.
Teoretycznie zawsze można uniknąć więcej niż dwóch poziomów zagnieżdżenia (poprzez wyraźne zaznaczenie wszystkich zamknięć), w praktyce używaj tyle, ile jest to uzasadnione.
Można również użyć funkcji pomocniczych dla tego rodzaju częściowego zastosowania , jak
_.partial
z podkreśleniem / lodash lub rodzimej.bind()
metody , w celu dalszego zmniejszenia wcięcia:źródło
bind
funkcja Monad. Haskell zapewnia cukier składniowy (notacja), aby wyglądał jak składnia asynchroniczna / oczekująca.Jawne przekazywanie
Podobnie jak zagnieżdżanie wywołań zwrotnych, technika ta opiera się na zamknięciach. Łańcuch pozostaje jednak płaski - zamiast przekazywać tylko najnowszy wynik, dla każdego kroku przekazywany jest jakiś obiekt stanu. Te obiekty stanu gromadzą wyniki poprzednich działań, przekazując wszystkie wartości, które będą potrzebne później, plus wynik bieżącego zadania.
Tutaj ta mała strzałka
b => [resultA, b]
jest funkcją, która zamyka sięresultA
i przekazuje tablicę obu wyników do następnego kroku. Który używa składni destrukcyjnej parametru, aby ponownie podzielić ją na pojedyncze zmienne.Zanim destrukcja stała się dostępna w ES6,
.spread()
wiele obiecujących bibliotek ( Q , Bluebird , kiedy …) zapewniało sprytną metodę pomocniczą, nazywaną Pobiera funkcję z wieloma parametrami - po jednym dla każdego elementu tablicy - do użycia jako.spread(function(resultA, resultB) { …
.Oczywiście to konieczne zamknięcie można dodatkowo uprościć dzięki niektórym funkcjom pomocniczym, np
Alternatywnie możesz zastosować
Promise.all
obietnicę dla tablicy:I możesz nie tylko używać tablic, ale dowolnie złożonych obiektów. Na przykład z
_.extend
lubObject.assign
w innej funkcji pomocniczej:Chociaż ten wzór gwarantuje płaski łańcuch, a jawne obiekty stanu mogą poprawić przejrzystość, stanie się nudny dla długiego łańcucha. Zwłaszcza, gdy potrzebujesz stanu sporadycznie, wciąż musisz przejść go przez każdy krok. Dzięki temu stałemu interfejsowi pojedyncze wywołania zwrotne w łańcuchu są raczej ściśle powiązane i nieelastyczne do zmiany. Utrudnia faktoring pojedynczych kroków, a wywołania zwrotne nie mogą być dostarczane bezpośrednio z innych modułów - zawsze muszą być owinięte kodem typu „kocioł”, który dba o stan. Funkcje abstrakcyjnego pomocnika takie jak powyżej mogą nieco złagodzić ból, ale zawsze będą obecne.
źródło
Promise.all
należy zachęcać do pominięcia składni (nie będzie działać w ES6, gdy zastąpienie go przez restrukturyzację i zmiana.spread
na athen
daje ludziom często nieoczekiwane wyniki. W związku z rozszerzeniem - nie jestem pewien, dlaczego potrzebujesz korzystać z rozszerzenia - dodawanie elementów do prototypu obietnicy nie jest akceptowalnym sposobem na rozszerzenie obietnic ES6, które i tak powinny zostać rozszerzone o (obecnie nieobsługiwane) podklasyPromise.all
”? Żadna z metod opisanych w tej odpowiedzi nie będzie działać z ES6. Przełączaniespread
do destructuringthen
nie powinien mieć problemów albo. Re .prototype.augment: Wiedziałem, że ktoś to zauważy, po prostu lubiłem odkrywać możliwości - je edytować.return [x,y]; }).spread(...
zamiast tegoreturn Promise.all([x, y]); }).spread(...
nie zmieniłaby się podczas zamiany spreadu na cukier destrukcyjny es6, a także nie byłaby dziwnym przypadkiem, w którym obietnice traktują zwracane tablice inaczej niż wszystko inne.Zmienny stan kontekstowy
Trywialnym (ale nieeleganckim i raczej podatnym na błędy) rozwiązaniem jest po prostu użycie zmiennych o wyższym zakresie (do których mają dostęp wszystkie wywołania zwrotne w łańcuchu) i zapisanie do nich wartości wyników, gdy je otrzymasz:
Zamiast wielu zmiennych można również użyć (początkowo pustego) obiektu, w którym wyniki są przechowywane jako dynamicznie tworzone właściwości.
To rozwiązanie ma kilka wad:
Biblioteka Bluebird zachęca do używania przekazywanego obiektu, używając swojej
bind()
metody do przypisania obiektu kontekstu do łańcucha obietnicy. Będzie dostępny z każdej funkcji zwrotnej za pomocąthis
słowa kluczowego, którego w innym przypadku nie można użyć . Podczas gdy właściwości obiektu są bardziej podatne na niewykryte literówki niż zmienne, wzór jest dość sprytny:To podejście można łatwo zasymulować w bibliotekach obiecujących, które nie obsługują .bind (choć w nieco bardziej szczegółowy sposób i nie można ich użyć w wyrażeniu):
źródło
.bind()
jest niepotrzebny do zapobiegania wyciekom pamięciMniej ostry spin w „Zmiennym stanie kontekstowym”
Użycie obiektu o lokalnym zasięgu do zebrania wyników pośrednich w łańcuchu obietnic jest rozsądnym podejściem do postawionego pytania. Rozważ następujący fragment kodu:
źródło
Promise
konstruktora antipattern !Węzeł 7.4 obsługuje teraz asynchroniczne / oczekujące połączenia z flagą harmonii.
Spróbuj tego:
i uruchom plik za pomocą:
node --harmony-async-await getExample.js
Proste jak może być!
źródło
W dzisiejszych czasach mam również pytania podobne do ciebie. W końcu znalazłem dobre rozwiązanie z pytaniem, jest proste i dobre do przeczytania. Mam nadzieję, że ci to pomoże.
Zgodnie z obietnicą how-to-chain-javascript
ok, spójrzmy na kod:
źródło
.then
przywołanej obietnicy , ale wynik wcześniejszego. Np. UzyskaniethirdPromise
dostępu do wynikufirstPromise
.Inna odpowiedź, przy użyciu
babel-node
wersji <6Za pomocą
async - await
npm install -g [email protected]
example.js:
Następnie biegnij
babel-node example.js
i voila!źródło
Nie zamierzam używać tego wzorca we własnym kodzie, ponieważ nie jestem wielkim fanem używania zmiennych globalnych. Jednak w mgnieniu oka to zadziała.
Użytkownik jest obiecanym modelem Mongoose.
źródło
globalVar
, po prostuUser.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });
?Inna odpowiedź przy użyciu sekwencyjnego modułu wykonującego nsynjs :
Aktualizacja: dodano działający przykład
źródło
Korzystając z Bluebird, możesz użyć
.bind
metody udostępniania zmiennych w łańcuchu obietnic:proszę sprawdzić ten link w celu uzyskania dalszych informacji:
http://bluebirdjs.com/docs/api/promise.bind.html
źródło
prosty sposób: D
źródło
Myślę, że możesz użyć skrótu RSVP.
Coś jak poniżej:
źródło
Promise.all
roztworze , tylko z obiektem zamiast tablicy.Rozwiązanie:
Możesz wstawić wartości pośrednie do zakresu w dowolnej późniejszej funkcji „następnie”, jawnie, używając „bind”. To miłe rozwiązanie, które nie wymaga zmiany sposobu działania Promises i wymaga jedynie wiersza lub dwóch kodów do propagowania wartości, tak jak błędy są już propagowane.
Oto kompletny przykład:
To rozwiązanie można wywołać w następujący sposób:
(Uwaga: przetestowano bardziej złożoną i kompletną wersję tego rozwiązania, ale nie ta przykładowa wersja, więc może mieć błąd).
źródło
async
/await
nadal oznacza stosowanie obietnic. To, co możesz porzucić, tothen
połączenia z oddzwanianiem.Czego się uczę o obietnicach, to używania ich tylko jako wartości zwracanych, jeśli to możliwe, unikania odwoływania się do nich . Składnia async / await jest do tego szczególnie praktyczna. Dzisiaj obsługują go wszystkie najnowsze przeglądarki i węzły: https://caniuse.com/#feat=async-functions , to proste zachowanie, a kod przypomina czytanie kodu synchronicznego, zapomnij o wywołaniach zwrotnych ...
W przypadkach, w których muszę odwoływać się do obietnic, jest to, że tworzenie i rozwiązywanie ma miejsce w niezależnych / niezwiązanych miejscach. Dlatego zamiast sztucznego powiązania i prawdopodobnie detektora zdarzeń w celu rozwiązania „odległej” obietnicy, wolę ujawnić obietnicę jako Odroczoną, którą poniższy kod implementuje w prawidłowym es5
transpilowane z mojego projektu maszynopisu:
https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31
W bardziej skomplikowanych przypadkach często używam tych małych narzędzi do obietnic bez testowania i pisania zależności. Mapa p była kilkakrotnie użyteczna. Myślę, że objął większość przypadków użycia:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
źródło