Jak uzyskać dostęp do wyników poprzednich obietnic w łańcuchu .then ()?

650

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?
    });
}
Bergi
źródło
2
To pytanie jest naprawdę interesujące i nawet jeśli jest otagowane javascript, ma znaczenie w innym języku. Po prostu używam odpowiedzi „przerwać łańcuch” w java i jdeferred
gontard

Odpowiedzi:

377

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ę.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Zamiast destructuring parametrów w zwrotnego po Promise.allktóre stały się dostępne tylko z ES6 w ES5 thenwezwanie 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ą joinfunkcję, która zastępuje tę kombinację Promise.all+ spreadprostszą (i bardziej wydajną) konstrukcją:


return Promise.join(a, b, function(resultA, resultB) {  });
Bergi
źródło
1
Czy funkcje wewnątrz tablicy są wykonywane po kolei?
straszny
6
@scaryguy: W tablicy nie ma żadnych funkcji, to obietnice. promiseAi promiseBsą tutaj funkcje (zwracające obietnicę).
Bergi
2
@Roland Nigdy nie powiedziałem, że było :-) Ta odpowiedź została napisana w erze ES5, w której żadne obietnice nie były w ogóle w standardzie i spreadbył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.
Bergi,
1
@reify Nie, nie powinieneś tego robić , spowodowałoby to kłopoty z odrzuceniem.
Bergi,
1
Nie rozumiem tego przykładu. Jeśli istnieje łańcuch instrukcji „następnie”, które wymagają propagowania wartości w całym łańcuchu, nie widzę, jak to rozwiązuje problem. Obietnicy wymagającej poprzedniej wartości NIE MOŻNA odpalić (utworzyć), dopóki ta wartość nie będzie obecna. Ponadto Promise.all () po prostu czeka na zakończenie wszystkich obietnic na liście: nie narzuca zamówienia. Potrzebuję więc każdej funkcji „następnej”, aby mieć dostęp do wszystkich poprzednich wartości i nie widzę, jak robi to twój przykład. Powinieneś poprowadzić nas przez swój przykład, ponieważ nie wierzę ani nie rozumiem.
David Spector
238

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 thenwywoł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:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

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 yieldsł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.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

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ą doadnotacje podobne do Haskella (np. LatteJs , monadic , PureScript lub LispyScript ).

Bergi
źródło
@Bergi czy musisz poczekać na funkcję asynchroniczną przykład getExample () z kodu zewnętrznego?
arisalexis
@arisalexis: Tak, getExamplenadal jest funkcją, która zwraca obietnicę, działa podobnie jak funkcje w innych odpowiedziach, ale ma ładniejszą składnię. Możesz awaitwywołać inną asyncfunkcję lub powiązać .then()jej wynik.
Bergi,
1
Jestem ciekawy, dlaczego odpowiedziałeś na swoje pytanie natychmiast po zadaniu? Jest tu dobra dyskusja, ale jestem ciekawa. Może sam znalazłeś odpowiedzi po zapytaniu?
granmoe
@granmoe: Celowo zamieściłem całą dyskusję jako kanoniczny duplikat
Bergi
Czy istnieje (niezbyt pracochłonny) sposób na uniknięcie korzystania z Promise.coroutine (tj. Nie używanie Bluebirda lub innej biblioteki, ale tylko zwykły JS) w przykładzie ECMAScript 6 z funkcją generatora? Miałem na myśli coś takiego, steps.next().value.then(steps.next)...ale to nie działało.
uczeń nie ma imienia
102

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ę.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

Można tego użyć do dowolnej liczby wartości:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}
Esailija
źródło
6
Oto moja ulubiona odpowiedź: czytelna, rozszerzalna i minimalna zależność od funkcji bibliotecznych lub językowych
Jason
13
@Jason: Uh, „ minimalne poleganie na funkcjach biblioteki ”? Kontrola synchroniczna jest funkcją biblioteki i dość niestandardową do uruchomienia.
Bergi,
2
Myślę, że miał na myśli funkcje specyficzne dla biblioteki
deathgaze
54

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.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

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.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Można również użyć funkcji pomocniczych dla tego rodzaju częściowego zastosowania , jak _.partialz podkreśleniem / lodash lub rodzimej .bind()metody , w celu dalszego zmniejszenia wcięcia:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}
Bergi
źródło
5
Tę samą sugestię podano jako rozwiązanie „Zaawansowanego błędu nr 4” w artykule Nolana Lawsona na temat obietnic pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . To dobra lektura.
Robert
2
Jest to dokładnie bindfunkcja Monad. Haskell zapewnia cukier składniowy (notacja), aby wyglądał jak składnia asynchroniczna / oczekująca.
zeronone
50

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.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Tutaj ta mała strzałka b => [resultA, b]jest funkcją, która zamyka się resultAi 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

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}


return promiseB(…).then(addTo(resultA));

Alternatywnie możesz zastosować Promise.allobietnicę dla tablicy:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

I możesz nie tylko używać tablic, ale dowolnie złożonych obiektów. Na przykład z _.extendlub Object.assignw innej funkcji pomocniczej:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

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.

Bergi
źródło
Po pierwsze, nie sądzę, że Promise.allnależy zachęcać do pominięcia składni (nie będzie działać w ES6, gdy zastąpienie go przez restrukturyzację i zmiana .spreadna a thendaje 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) podklasy
Benjamin Gruenbaum
@BenjaminGruenbaum: Co rozumiesz przez „ pominięcie składniPromise.all ”? Żadna z metod opisanych w tej odpowiedzi nie będzie działać z ES6. Przełączanie spreaddo destructuring thennie 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ć.
Bergi,
Mam na myśli składnię tablic, która return [x,y]; }).spread(...zamiast tego return 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.
Benjamin Gruenbaum,
3
To prawdopodobnie najlepsza odpowiedź. Obietnice to „Funkcjonalne programowanie reaktywne” - i często jest to stosowane rozwiązanie. Na przykład BaconJs ma #combineTemplate, który pozwala łączyć wyniki w obiekt przekazywany w dół łańcucha
U Avalos
1
@CapiEtheriel Odpowiedź została napisana, gdy ES6 nie był tak szeroko rozpowszechniony jak obecnie. Tak, może czas zamienić przykłady
Bergi
35

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:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

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:

  • Zmienny stan jest brzydki , a zmienne globalne złe .
  • Ten wzorzec nie działa ponad granicami funkcji, modularyzacja funkcji jest trudniejsza, ponieważ ich deklaracje nie mogą opuszczać wspólnego zakresu
  • Zakres zmiennych nie uniemożliwia dostępu do nich przed ich inicjalizacją. Jest to szczególnie prawdopodobne w przypadku złożonych konstrukcji obietnic (pętle, rozgałęzienia, wyjątki), w których mogą wystąpić warunki wyścigu. Przejście stanu jawnie, deklaratywny projekt, który obiecuje zachęcić, wymusza czystszy styl kodowania, który może temu zapobiec.
  • Należy poprawnie wybrać zakres dla tych współdzielonych zmiennych. Musi być lokalny dla wykonywanej funkcji, aby zapobiec warunkom wyścigu między wieloma równoległymi wywołaniami, tak jak gdyby na przykład stan był przechowywany w instancji.

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ą thissł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:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

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):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}
Bergi
źródło
.bind()jest niepotrzebny do zapobiegania wyciekom pamięci
Esailija
@Esailija: Ale czy zwrócona obietnica nie zawiera odniesienia do obiektu kontekstu inaczej? OK, oczywiście śmiecianie sobie z tym poradzą; nie jest to „wyciek”, chyba że obietnica nigdy nie zostanie spełniona.
Bergi,
Tak, ale obietnice odnoszą się również do ich wartości spełnienia i przyczyn błędów ... ale nic nie ma odniesienia do obietnicy, więc to nie ma znaczenia
Esailija
4
Proszę podzielić tę odpowiedź na dwie części, ponieważ prawie głosowałem w sprawie preambuły! Myślę, że „trywialne (ale nieeleganckie i raczej podatne na błędy) rozwiązanie” jest najczystszym i najprostszym rozwiązaniem, ponieważ nie opiera się bardziej na zamknięciach i stanie zmiennym niż zaakceptowana odpowiedź, ale jest prostsza. Zamknięcia nie są ani globalne, ani złe. Argumenty przeciwko takiemu podejściu nie mają dla mnie sensu, biorąc pod uwagę tę przesłankę. Jakie problemy związane z modularyzacją mogą mieć „wspaniały długi, płaski łańcuch obietnic”?
wysięgnik
2
Jak powiedziałem powyżej, obietnice są światłem „Funkcjonalnego programowania reaktywnego”. To jest anty-wzór we FRP
U Avalos
15

Mniej 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:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • Zmienne globalne są złe, dlatego w tym rozwiązaniu zastosowano zmienną o zasięgu lokalnym, która nie powoduje żadnych szkód. Jest dostępny tylko w ramach funkcji.
  • Stan zmienny jest brzydki, ale nie mutuje go w brzydki sposób. Brzydki zmienny stan tradycyjnie odnosi się do modyfikowania stanu argumentów funkcji lub zmiennych globalnych, ale to podejście po prostu modyfikuje stan zmiennej o zasięgu lokalnym, która istnieje wyłącznie w celu agregowania wyników obietnicy ... zmiennej, która umrze zwykłą śmiercią po spełnieniu obietnicy.
  • Pośrednie obietnice nie mają uniemożliwienia dostępu do stanu obiektu wyników, ale nie wprowadza to przerażającego scenariusza, w którym jedna z obietnic w łańcuchu stanie się nieuczciwa i sabotuje twoje wyniki. Odpowiedzialność za ustawienie wartości na każdym etapie obietnicy ogranicza się do tej funkcji, a ogólny wynik będzie albo poprawny, albo niepoprawny ... to nie będzie jakiś błąd, który pojawi się lata później w produkcji (chyba że masz zamiar to zrobić !)
  • Nie wprowadza to scenariusza warunków wyścigu, który powstałby w wyniku równoległego wywołania, ponieważ dla każdego wywołania funkcji getExample tworzona jest nowa instancja zmiennej wyników.
Sójka
źródło
1
Unikaj przynajmniej Promisekonstruktora antipattern !
Bergi,
Dzięki @Bergi, nawet nie zdawałem sobie sprawy, że to był anty-wzór, dopóki o nim nie wspomniałeś!
Jay
jest to dobre obejście problemu w celu zmniejszenia błędu związanego z obietnicą. Używałem ES5 i nie chciałem dodawać innej biblioteki do pracy z obietnicą.
nilakantha singh deo
8

Węzeł 7.4 obsługuje teraz asynchroniczne / oczekujące połączenia z flagą harmonii.

Spróbuj tego:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

i uruchom plik za pomocą:

node --harmony-async-await getExample.js

Proste jak może być!

Anthony
źródło
8

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:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });
yzfdjzwl
źródło
4
To tak naprawdę nie odpowiada na pytanie, jak uzyskać dostęp do poprzednich wyników w łańcuchu.
Bergi,
2
Każda obietnica może uzyskać poprzednią wartość, jakie jest twoje znaczenie?
yzfdjzwl
1
Spójrz na kod w pytaniu. Celem nie jest uzyskanie rezultatu .thenprzywołanej obietnicy , ale wynik wcześniejszego. Np. Uzyskanie thirdPromisedostępu do wyniku firstPromise.
Bergi
6

Inna odpowiedź, przy użyciu babel-nodewersji <6

Za pomocą async - await

npm install -g [email protected]

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Następnie biegnij babel-node example.jsi voila!

Anthony
źródło
1
Tak, zrobiłem to zaraz po tym, jak opublikowałem mój. Mimo to zamierzam to zostawić, ponieważ wyjaśnia, jak właściwie zacząć korzystać z ES7, a nie po prostu powiedzieć, że kiedyś będzie dostępna ES7.
Anthony
1
No tak, powinienem zaktualizować swoją odpowiedź, aby powiedzieć, że „eksperymentalne” wtyczki dla nich już są .
Bergi,
2

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.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
Anthony
źródło
2
Zauważ, że ten wzór jest już szczegółowo w zmienny kontekstowej państwowej odpowiedź (a także dlatego, że jest brzydka - nie jestem wielkim fanem albo)
Bergi
W twoim przypadku wzór wydaje się jednak bezużyteczny. W ogóle nie potrzebujesz globalVar, po prostu User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi,
1
Nie potrzebuję go osobiście w swoim własnym kodzie, ale użytkownik może potrzebować uruchomić więcej asynchronizacji w drugiej funkcji, a następnie wejść w interakcję z oryginalnym wywołaniem Promise. Ale jak wspomniano, w tym przypadku będę używać generatorów. :)
Anthony
2

Inna odpowiedź przy użyciu sekwencyjnego modułu wykonującego nsynjs :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Aktualizacja: dodano działający przykład

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

amaksr
źródło
1

Korzystając z Bluebird, możesz użyć .bindmetody udostępniania zmiennych w łańcuchu obietnic:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

proszę sprawdzić ten link w celu uzyskania dalszych informacji:

http://bluebirdjs.com/docs/api/promise.bind.html

alphakevin
źródło
Zauważ, że ten wzór jest już szczegółowo w zmienny kontekstowej państwowej odpowiedź
Bergi
1
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

prosty sposób: D

Minh Giang
źródło
Zauważyłeś tę odpowiedź ?
Bergi,
1

Myślę, że możesz użyć skrótu RSVP.

Coś jak poniżej:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });
Vishu
źródło
Tak, to jest taki sam jak w Promise.allroztworze , tylko z obiektem zamiast tablicy.
Bergi,
0

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:

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

To rozwiązanie można wywołać w następujący sposób:

pLogInfo("local info").then().catch(err);

(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).

David Spector
źródło
To wydaje się być taki sam wzór jak w zagnieżdżenia (a) closures odpowiedź
Bergi
Wygląda podobnie. Od tego czasu dowiedziałem się, że nowa składnia Async / Await obejmuje automatyczne wiązanie argumentów, więc wszystkie argumenty są dostępne dla wszystkich funkcji asynchronicznych. Porzucam obietnice.
David Spector
async/ awaitnadal oznacza stosowanie obietnic. To, co możesz porzucić, to thenpołączenia z oddzwanianiem.
Bergi,
-1

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

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

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=

rakbero
źródło
Brzmi, jakbyś sugerował albo zmienny stan kontekstowy, albo synchroniczną kontrolę ?
Bergi,
@bergi Po raz pierwszy kieruję tymi nazwami. dodając do listy dzięki. Znam tego rodzaju samoświadome obietnice o nazwie Odroczony - BTW wdrożenie jest tylko obietnicą z zapakowanym postanowieniem. Często potrzebuję tego wzorca w tych przypadkach, w których odpowiedzialność za tworzenie obietnic i ich rozwiązywanie są niezależne, więc nie trzeba ich odnosić tylko po to, aby rozwiązać obietnicę. Zaadaptowałem się, ale nie dla twojego przykładu, i używając klasy, ale może równoważnej.
cancerbero