Bir .then () zincirinde önceki vaat sonuçlarına nasıl erişirim?


650

Kodumu vaatler için yeniden yapılandırdım ve birden fazla geri aramadan oluşan harika bir uzun düz söz zinciri oluşturdum .then(). Sonunda bir miktar bileşik değer döndürmek istiyorum ve birden fazla ara vaat sonuçlarına erişmek gerekiyor . Ancak dizinin ortasındaki çözünürlük değerleri son geri aramada kapsam dışında değildir, bunlara nasıl erişebilirim?

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?
    });
}

2
Bu soru gerçekten ilginç ve etiketlenmiş olsa bile javascript, diğer dilde alakalı. Ben sadece java ve jdeferred "zinciri kırmak" cevabını kullanıyorum
gontard

Yanıtlar:


377

Zinciri kır

Zincirinizdeki ara değerlere erişmeniz gerektiğinde, zincirinizi ihtiyacınız olan tek parçalara ayırmalısınız. Bir geri arama eklemek ve bir şekilde parametresini birden çok kez kullanmaya çalışmak yerine, sonuç değerine ihtiyacınız olan her yere aynı vaat için birden fazla geri arama ekleyin. Unutmayın, bir söz sadece gelecekteki bir değeri temsil eder (vekil) ! Doğrusal bir zincirde diğerinden bir vaat türetmenin yanında, sonuç değerini oluşturmak için kitaplığınız tarafından size verilen söz birleştiricileri kullanın.

Bu, çok basit bir kontrol akışı, net işlevsellik kompozisyonu ve dolayısıyla kolay modülerleştirme ile sonuçlanacaktır.

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
    });
}

Bunun yerine sonra geri aramasında parametre Strüktür kırıcı bir Promise.alltek ES6 ile mevcut haline geldiğini, ES5 içinde thençağrı birçok vaadi kütüphaneleri tarafından sağlanan şık bir yardımcı yöntem ile yerini olacağını ( Q , Bluebird , ne zaman , ...): .spread(function(resultA, resultB) { ….

Bluebird ayrıca bu + kombinasyonunu daha basit (ve daha verimli) bir yapı ile değiştirmek için özel bir joinişleve sahiptir :Promise.allspread


return Promise.join(a, b, function(resultA, resultB) {  });

1
Dizinin içindeki işlevler sırayla yürütülüyor mu?
scaryguy

6
@scaryguy: Dizide işlev yok, bunlar vaatler. promiseAve promiseBburada (söz veren) işlevlerdir.
Bergi

2
@Roland Asla öyle olduğunu söylemedi :-) Bu cevap, hiçbir sözün standartta olmadığı ES5 çağında yazılmıştır ve spreadbu modelde süper yararlı olmuştur. Daha modern çözümler için kabul edilen cevaba bakınız. Ancak, açık geçişli cevabı zaten güncelledim ve bunu da güncellememek için gerçekten iyi bir neden yok.
Bergi

1
@reify Hayır, bunu yapmamalısın , reddedilme konusunda sorun çıkarır .
Bergi

1
Bu örneği anlamıyorum. Bu değerlerin zincir boyunca yayılmasını gerektiren 'then' ifadelerinden oluşan bir zincir varsa, bunun sorunu nasıl çözdüğünü göremiyorum. Önceki bir değer gerektiren bir Söz, bu değer bulunana kadar BAŞLATILAMAZ (oluşturulamaz). Ayrıca, Promise.all () basitçe listesindeki tüm vaatlerin bitmesini bekler: emir vermez. Bu yüzden önceki tüm değerlere erişebilmek için her bir 'sonraki' fonksiyonuna ihtiyacım var ve örneğinizin bunu nasıl yaptığını göremiyorum. Bizi örneğinizden geçirmelisiniz, çünkü inanmıyorum ya da anlamıyorum.
David Spector

238

ECMAScript Uyumu

Tabii ki, bu sorun dil tasarımcıları tarafından da kabul edildi. Çok fazla iş yaptılar ve asenkron fonksiyonlar önerisi nihayetinde

ECMAScript 8

Artık tek bir thençağrı veya geri çağırma işlevine ihtiyacınız yoktur , eşzamansız bir işlevde (çağrıldığında bir vaat döndüren) olduğu gibi, sözlerin doğrudan çözülmesini bekleyebilirsiniz. Ayrıca koşullar, döngüler ve try-catch-yan tümceleri gibi keyfi kontrol yapılarına sahiptir, ancak kolaylık sağlamak için burada bunlara ihtiyacımız yoktur:

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

ECMAScript 6

ES8'i beklerken çok benzer bir sözdizimi kullandık. ES6 , keyfi olarak yerleştirilmiş anahtar kelimelerde yürütmeyi parçalara ayırabilen jeneratör işlevleri ile birlikte geldi yield. Bu dilimler birbiri ardına, bağımsız, hatta eşzamansız olarak çalıştırılabilir - ve bir sonraki adımı çalıştırmadan önce bir vaat çözümü beklemek istediğimizde yaptığımız şey budur.

Adanmış kütüphaneler ( co veya task.js gibi ) vardır, ancak aynı zamanda birçok vaat kütüphanesinde, jeneratör oluşturduğunuzda bu asenkron işlemi sizin için adım adım yürüten yardımcı işlevlere ( Q , Bluebird , when ,…) sahiptir . vaatler verir.

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
});

Bu sürüm 4.0'dan beri Node.js'de işe yaradı, ayrıca birkaç tarayıcı (veya geliştirici sürümleri) jeneratör sözdizimini nispeten erken destekledi.

ECMAScript 5

Ancak, geriye dönük olarak uyumlu olmak / gereksiniminiz varsa, transpiler olmadan bunları kullanamazsınız. Hem jeneratör fonksiyonları hem de asenkron fonksiyonlar mevcut takım tarafından desteklenir, örneğin Babel'in jeneratörler ve asenkron fonksiyonlar hakkındaki belgelerine bakın .

Ve sonra, senkronize olmayan programlamayı kolaylaştırmaya adanmış birçok derleme-JS dili de var. Genellikle await, (ör. Iced CoffeeScript ) ile benzer bir sözdizimi kullanırlar , ancak Haskell benzeri bir donotasyon (örneğin LatteJ'ler , monadik , PureScript veya LispyScript ) içeren başka sözdizimleri de vardır .


@Bergi dış koddan getExample () kodunu incelemek için zaman uyumsuz işlevi beklemeniz gerekiyor mu?
arisalexis

@arisalexis: Evet, getExamplediğer yanıtlardaki işlevler gibi çalışan ancak daha güzel sözdizimiyle çalışan bir söz veren bir işlevdir. awaitBaşka bir asyncişlevde çağrı yapabilir veya .then()sonucunu zincirleyebilirsiniz .
Bergi

1
Merak ediyorum, neden sorduktan hemen sonra kendi sorunuzu cevapladınız? Burada iyi bir tartışma var ama merak ediyorum. Belki sorduktan sonra cevaplarınızı kendi başınıza buldunuz?
granmoe

@granmoe: Bütün tartışmayı bilerek kanonik yinelenen bir hedef olarak yayınladım
Bergi

ECMAScript 6 örneğinde jeneratör fonksiyonu ile Promise.coroutine (yani Bluebird veya başka bir kütüphane değil, sadece düz JS kullanmamak) için kaçınmanın (çok zahmetli olmayan) bir yolu var mı? Aklımda bir şey vardı steps.next().value.then(steps.next)...ama işe yaramadı.
bir öğrencinin adı yok

102

Senkron muayene

Değişkenlere daha sonra ihtiyaç duyulan değerler için vaatler vermek ve ardından değerlerini eşzamanlı inceleme yoluyla almak. Örnekte bluebird .value()yöntemi kullanılmıştır ancak birçok kütüphane benzer bir yöntem sunmaktadır.

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();
    });
}

Bu, istediğiniz kadar değer için kullanılabilir:

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();
    });
}

6
Bu benim en sevdiğim cevap: okunabilir, genişletilebilir ve kütüphane veya dil özelliklerine en az güven
Jason

13
@Jason: Ah, " kütüphane özelliklerine en az güven "? Eşzamanlı inceleme bir kütüphane özelliğidir ve önyükleme için oldukça standart olmayan bir özelliktir.
Bergi

2
Bence kütüphaneye özgü özellikler demekti
deathgaze

54

Yerleştirme (ve) kapanışları

Değişkenlerin kapsamını korumak için kapakların kullanılması (bizim durumumuzda, başarı geri çağırma işlevi parametreleri) doğal JavaScript çözümüdür. Sözlerle, geri çağrıları keyfi olarak iç içe geçirebilir ve düzeltebiliriz .then()- içselin kapsamı dışında, anlamsal olarak eşdeğerdirler.

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

Tabii ki, bu bir girinti piramidi inşa ediyor. Girinti çok büyüyorsa, kıyamet piramidine karşı koymak için eski araçları uygulayabilirsiniz : modülerleştirin, ekstra adlandırılmış işlevler kullanın ve artık bir değişkene ihtiyacınız olmadığında vaat zincirini düzleştirin.
Teoride, pratikte mantıklı olduğu kadar her zaman ikiden fazla iç içe yerleştirme seviyesinden (tüm kapakların açık olmasını sağlayarak) kaçınabilirsiniz.

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
    };
}

Ayrıca, bu tür bir yardımcı fonksiyonları kullanarak kısmi uygulama gibi, _.partialgelen Underscore / lodash veya doğal .bind()bir yöntem bundan başka bir azalma girinti için:

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
}

5
Bu aynı öneri Nolan Lawson'un vaatlerle ilgili makalesinde 'Gelişmiş hata # 4'e çözüm olarak verilmiştir . Pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html . İyi bir okuma.
Robert

2
Bu bindMonads'taki işlevdir. Haskell, sözdizimsel şeker (do-notasyon) sağlar, bu sözdizimi asenkron / bekliyor sözdizimine benzetir.
zeronone

50

Açık geçiş

Geri aramaların yuvalanmasına benzer şekilde, bu teknik kapaklara dayanır. Ancak, zincir düz kalır - sadece en son sonucu geçmek yerine, her adım için bir durum nesnesi geçirilir. Bu durum nesneleri, daha sonra gerekli olacak tüm değerleri ve geçerli görevin sonucunu teslim ederek önceki eylemlerin sonuçlarını biriktirir.

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
    });
}

Burada, bu küçük ok b => [resultA, b], resultAher iki sonucun bir dizisini bir sonraki adıma geçiren işlevdir . Bu, tek değişkenlerde tekrar parçalamak için parametre yıkım sözdizimini kullanır.

ES6 ile yıkım yapılmadan önce, .spread()birçok söz kütüphanesi ( Q , Bluebird , when ,…) tarafından adlandırılan şık bir yardımcı yöntem sağlandı . Kullanılacak her dizi öğesi için bir tane olmak üzere birden fazla parametreli bir işlev alır .spread(function(resultA, resultB) { ….

Elbette, burada ihtiyaç duyulan kapanma, bazı yardımcı işlevlerle daha da basitleştirilebilir, örn.

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

Alternatif olarak, Promise.alldizi için vaat üretmek için kullanabilirsiniz :

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
    });
}

Ve sadece dizileri değil, keyfi olarak karmaşık nesneleri de kullanabilirsiniz. Örneğin, farklı bir yardımcı işlevle _.extendveya bu Object.assignişlevde:

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
    });
}

Bu model düz bir zinciri garanti ederken ve açık durumdaki nesneler netliği artırabilirken, uzun bir zincir için sıkıcı olacaktır. Özellikle devlete sadece ara sıra ihtiyaç duyduğunuzda, hala her adımdan geçmeniz gerekiyor. Bu sabit arabirimle, zincirdeki tek geri çağrılar oldukça sıkı bir şekilde bağlanır ve değişime esnek değildir. Faktörleri tek adımlarla zorlaştırır ve geri çağrılar doğrudan diğer modüllerden sağlanamaz - her zaman durumu önemseyen demirbaş koduna sarılmalıdır. Yukarıdaki gibi soyut yardımcı işlevler ağrıyı biraz hafifletebilir, ancak her zaman mevcut olacaktır.


Birincisi, sözdizimi atlayarak sanmıyorum Promise.allo yapı bozma bunu ES6 değil işi yerine geçecek zaman (teşvik edilmelidir ve bir anahtarlama .spreada thensık sık beklenmedik sonuçlar insanlar verir arttırdıklarında itibariyle -. Ben emin neden ihtiyaç değilim artırmayı kullanmak - söz prototipine bir şeyler eklemek, zaten (şu anda desteklenmeyen) alt sınıflandırma ile genişletilmesi gereken ES6 sözlerini uzatmanın kabul edilebilir bir yolu değildir
Benjamin Gruenbaum

@BenjaminGruenbaum: " Sözdizimi ihmalPromise.all " ile ne demek istiyorsun ? Bu yanıttaki yöntemlerin hiçbiri ES6 ile kesilmez. spreadBir yıkıma geçiş yapmak thenda sorun yaratmamalıdır. Re .prototype.augment: Birisinin fark edeceğini biliyordum, sadece olasılıkları keşfetmeyi sevdim - onu düzenleyeceğim.
Bergi

Dizinin sözdizimiyle, return [x,y]; }).spread(...bunun yerine return Promise.all([x, y]); }).spread(...es6 yıkıcı şeker için yayılırken değişmeyeceği ve vaat edilen dizilerin diğer her şeyden farklı şekilde işlendiği vaatlerin olduğu garip bir kenar durumu olmayacağı anlamına geliyor.
Benjamin Gruenbaum

3
Bu muhtemelen en iyi cevaptır. Vaatler "Fonksiyonel Reaktif Programlama" -lighttır ve bu genellikle kullanılan çözümdür. Örneğin, BaconJs, sonuçları zincirden geçen bir nesnede birleştirmenize izin veren #combineTemplate'e sahiptir
U Avalos

1
@CapiEtheriel Cevap ES6 bugünkü kadar geniş olmadığında yazılmıştır. Evet, belki örnekleri değiştirmenin zamanı geldi
Bergi

35

Değişken bağlamsal durum

Önemsiz (ancak yetersiz ve oldukça hata eğilimli) çözüm, yalnızca daha yüksek kapsam değişkenlerini (zincirdeki tüm geri çağrıların erişebildiği) kullanmak ve bunları elde ettiğinizde sonuç değerlerini yazmaktır:

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
    });
}

Birçok değişken yerine, sonuçların dinamik olarak oluşturulmuş özellikler olarak depolandığı (başlangıçta boş) bir nesne de kullanılabilir.

Bu çözümün birkaç dezavantajı vardır:

  • Değişken durum çirkindir ve küresel değişkenler kötülüktür .
  • Bu desen işlev sınırları boyunca çalışmaz, bildirimleri paylaşılan kapsamdan ayrılmaması gerektiğinden işlevleri modüle etmek daha zordur
  • Değişkenlerin kapsamı, başlatılmadan önce bunlara erişilmesini engellemez. Bu, özellikle yarış koşullarının meydana gelebileceği karmaşık vaat yapıları (döngüler, dallanma, istisnalar) için muhtemeldir. Devletin açıkça geçmesi, söz veren cesaret verici bir tasarım , bunu önleyebilecek daha temiz bir kodlama stilini zorlar.
  • Paylaşılan değişkenlerin kapsamını doğru seçmelisiniz. Örneğin durumun bir örnekte depolanması durumunda olduğu gibi, birden fazla paralel çağırma arasındaki yarış koşullarını önlemek için yürütülen işlevde yerel olması gerekir.

Bluebird kütüphanesi kullanarak, birlikte geçirilir bir nesnenin kullanımını teşvik kendi bind()yöntem bir söz zincirine bir bağlam nesne atamak. Aksi takdirde kullanılamayan thisanahtar kelime aracılığıyla her geri arama işlevinden erişilebilir . Nesne özellikleri, algılanmamış yazım hatalarına değişkenlerden daha eğilimli olsa da, desen oldukça zekidir:

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
}

Bu yaklaşım, .bind'i desteklemeyen söz kitaplıklarında kolayca simüle edilebilir (biraz daha ayrıntılı bir şekilde ve bir ifadede kullanılamaz):

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));
}

.bind()bellek sızıntısını önlemek için gereksizdir
Esailija

@Esailija: Ancak geri verilen söz, aksi takdirde bağlam nesnesine bir referans içermiyor mu? Tamam, elbette çöp toplama daha sonra halledecek; söz asla bertaraf edilmediği sürece bir "sızıntı" değildir.
Bergi

Evet ama vaatler aynı zamanda onların yerine getirme değerleri ve hata nedenlerine de atıfta bulunuyor ... ama hiçbir şey
vaatlere

4
Neredeyse önsözde oy kullandığım için lütfen bu cevabı ikiye ayırın! Bence "önemsiz (ama yanlış ve oldukça hata eğilimli) çözüm" en temiz ve en basit çözümdür, çünkü kabul edilen kendi cevabınızdan daha fazla kapanmaya ve değişebilir duruma dayanmaz, ancak daha basittir. Kapanışlar ne küresel ne de kötüdür. Bu yaklaşıma aykırı argümanlar bana öncül olduğu için bir anlam ifade etmiyor. Hangi "modülerleştirme problemleri" harika uzun düz vaat zinciri "verilebilir?
pergel

2
Yukarıda söylediğim gibi, Vaatler "Fonksiyonel Reaktif Programlama" - ışık. Bu
FRP'de

15

"Değişken bağlamsal durum" konusunda daha az sert bir dönüş

Bir söz zincirinde ara sonuçları toplamak için yerel olarak kapsamlandırılmış bir nesne kullanmak, sorduğunuz soruya makul bir yaklaşımdır. Aşağıdaki snippet'i düşünün:

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);
    });
}
  • Global değişkenler kötüdür, bu nedenle bu çözüm hiçbir zarar vermeyen yerel olarak kapsamlandırılmış bir değişken kullanır. Yalnızca işlev içinde erişilebilir.
  • Değişken durum çirkin, ama bu durum çirkin bir şekilde mutasyona uğramaz. Çirkin değişebilir durum geleneksel olarak işlev argümanlarının veya küresel değişkenlerin durumunu değiştirmeyi ifade eder, ancak bu yaklaşım sadece vaat sonuçlarını toplamak amacıyla var olan yerel olarak kapsamlandırılmış bir değişkenin durumunu basitçe değiştirir ... basit bir ölümü öldürebilecek bir değişken bir kez söz çözülür.
  • Ara vaatlerin sonuç nesnesinin durumuna erişmesi engellenmez, ancak bu, zincirdeki vaatlerden birinin hileli hale geleceği ve sonuçlarınızı sabote edeceği bazı korkunç senaryolar getirmez. Sözün her adımında değerleri belirleme sorumluluğu bu işlevle sınırlıdır ve genel sonuç doğru veya yanlış olacaktır ... üretimde yıllar sonra ortaya çıkacak bazı hatalar olmayacaktır ( !)
  • GetExample işlevinin her çağrılması için results değişkeninin yeni bir örneği oluşturulduğundan, bu durum paralel çağırma işleminden kaynaklanabilecek bir yarış koşulu senaryosu getirmez.

1
En azından Promiseyapıcı antipatterninden kaçının !
Bergi

Teşekkürler @Bergi, siz bahsetene kadar bunun bir anti-desen olduğunu fark etmedim!
Jay

Bu söz ile ilgili hatayı azaltmak için iyi bir çözümdür. ES5 kullanıyordum ve söz ile çalışmak için başka bir kütüphane eklemek istemedim.
nilakantha singh deo

8

7.4 Düğümü artık uyum bayrağıyla zaman uyumsuz / bekleyen çağrıları destekliyor.

Bunu dene:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

ve dosyayı şununla çalıştırın:

node --harmony-async-await getExample.js

Olabildiğince basit!


8

Bu günlerde senin gibi bazı sorularla da karşılaşıyorum. Sonunda, soru ile iyi bir çözüm buldum, okumak basit ve güzel. umuyorum ki bu sana yardım edebilir.

Göre nasıl yapılır-zinciri-javascript-vaatlerine

tamam, koda bakalım:

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);
    });

4
Bu, zincirdeki önceki sonuçlara nasıl erişileceği sorusuna gerçekten cevap vermez.
Bergi

2
Her söz bir önceki değeri alabilir, ne demek istiyorsun?
yzfdjzwl

1
Sorudaki koda bir göz atın. Amaç, .thençağrılan sözün sonucunu değil, ondan önceki sonuçları almaktır . Örneğin thirdPromisesonucuna erişim firstPromise.
Bergi

6

babel-node<6 sürümünü kullanan başka bir yanıt

kullanma async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Sonra, koş babel-node example.jsve voila!


1
Evet, benimkini gönderdikten hemen sonra yaptım. Yine de bırakacağım, çünkü bir gün ES7'nin kullanılabilir olacağını söylemenin aksine ES7'yi nasıl kullanmaya başlayacağınızı açıklıyor.
Anthony

1
Doğru, cevaplarımı güncellemeliyim ki bunlar için "deneysel" eklentiler zaten burada.
Bergi

2

Global değişkenleri kullanmanın büyük bir hayranı olmadığım için bu kodu kendi kodumda kullanmayacağım. Ancak, bir tutamda işe yarayacak.

Kullanıcı vaat edilmiş bir Firavun faresi modelidir.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});

2
Bu kalıbın Mutable bağlamsal durum cevabında zaten ayrıntılı olduğuna dikkat edin (ve ayrıca neden çirkin - ben de büyük bir hayran değilim)
Bergi

Sizin durumunuzda, desen yine de işe yaramaz gibi görünüyor. Hiç ihtiyacınız globalVaryok, sadece User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi

1
Kişisel olarak kendi koduma ihtiyacım yok, ancak kullanıcının ikinci işlevde daha fazla zaman uyumsuzluğu çalıştırması ve ardından orijinal Promise çağrısı ile etkileşime girmesi gerekebilir. Ama belirtildiği gibi, bu durumda jeneratör kullanacağım. :)
Anthony

2

Sıralı yürütücü nsynjs kullanarak başka bir cevap :

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');
})

Güncelleme: çalışma örneği eklendi

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>


1

Bluebird kullanırken .bind, vaat zincirindeki değişkenleri paylaşmak için yöntemi kullanabilirsiniz :

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;
});

daha fazla bilgi için lütfen bu bağlantıyı kontrol edin:

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



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?
    });
}

kolay yol: D


Bu yanıtı fark ettiniz mi?
Bergi

1

Sanırım RSVP hashini kullanabilirsiniz.

Aşağıdaki gibi bir şey:

    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);
    });

Evet, bu aynı şey çözümü sadece bir nesnenin yerine bir dizi. Promise.all
Bergi

0

Çözüm:

'Bind' kullanarak ara değerlerini daha sonra 'sonra' işlevine açıkça koyabilirsiniz. Promises'ın çalışma şeklini değiştirmeyi gerektirmeyen ve hataların zaten yayıldığı gibi değerleri yaymak için sadece bir veya iki kod satırı gerektiren güzel bir çözümdür.

İşte tam bir örnek:

// 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

Bu çözüm aşağıdaki gibi çağrılabilir:

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

(Not: Bu çözümün daha karmaşık ve eksiksiz bir sürümü test edilmiştir, ancak bu örnek sürüm değil, bu nedenle bir hata olabilir.)


Bu, iç içe geçme (ve) kapanış cevabı ile aynı desen gibi görünüyor
Bergi

Benzer görünüyor. O zamandan beri yeni Async / Await sözdiziminin argümanların otomatik olarak bağlanmasını içerdiğini öğrendim, bu yüzden tüm argümanlar tüm asenkron işlevler için kullanılabilir. Vaatlerden vazgeçiyorum.
David Spector

async/ awaithala vaat kullanmak anlamına gelir. Vazgeçebileceğiniz şey then, geri aramalarla yapılan aramalardır.
Bergi

-1

Vaatler hakkında öğrendiğim şey, sadece dönüş değerleri mümkünse onları referans almaktan kaçınmak için kullanmaktır . async / await sözdizimi bunun için özellikle pratiktir. Bugün tüm son tarayıcılar ve düğüm destekliyor: https://caniuse.com/#feat=async-functions , basit bir davranış ve kod senkron kodu okumak gibi, geri çağrıları unutun ...

Bir vaatlere başvurmam gerektiğinde, bağımsız / ilişkili olmayan yerlerde yaratılış ve çözümün gerçekleştiği zamandır. Bu yüzden yapay bir dernek ve muhtemelen "uzak" vaadi çözmek için bir olay dinleyicisi yerine, aşağıdaki kodun geçerli es5'te uyguladığı bir ertelenmiş olarak vaat etmeyi tercih ederim

/**
 * 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;
}());

bir daktilo projemden aktarıldı:

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

Daha karmaşık durumlar için, genellikle bu adam test edilen ve yazılan bağımlılıklar olmadan küçük söz programları kullanıyorum. p-haritası birkaç kez faydalı olmuştur. En çok kullanım durumlarını kapsadığını düşünüyorum:

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=


Senin gibi Sesler ya öneriyorsun değişken içeriksel devlet veya senkron muayene ?
Bergi

@bergi Bu isimlere ilk kez başkanlık ettiğimde, listeye ek olarak teşekkürler. Vaat oluşturma ve çözme sorumluluğunun bağımsız olduğu durumlarda genellikle bu kalıba ihtiyacım var, bu yüzden sadece bir vaadi çözmek için onları ilişkilendirmeye gerek yok. Uyarladım ama senin örneğin için değil, bir sınıf kullanarak, ama belki de eşdeğeri.
cancerbero
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.