Bagaimana cara mengakses hasil janji sebelumnya dalam rantai .then ()?

650

Saya telah merestrukturisasi kode saya menjadi janji , dan membangun rantai janji datar panjang yang indah , yang terdiri dari beberapa .then()panggilan balik. Pada akhirnya saya ingin mengembalikan beberapa nilai komposit, dan perlu mengakses beberapa hasil janji menengah . Namun nilai-nilai resolusi dari tengah urutan tidak dalam cakupan di panggilan balik terakhir, bagaimana cara mengaksesnya?

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
sumber
2
Pertanyaan ini sangat menarik dan bahkan jika ditandai javascript, itu relevan dalam bahasa lain. Saya hanya menggunakan jawaban "break the chain" di java dan jdeferred
gontard

Jawaban:

377

Hancurkan rantai

Ketika Anda perlu mengakses nilai-nilai perantara dalam rantai Anda, Anda harus membagi rantai Anda menjadi satu bagian yang Anda butuhkan. Alih-alih melampirkan satu panggilan balik dan entah bagaimana mencoba menggunakan parameternya beberapa kali, lampirkan beberapa panggilan balik ke janji yang sama - di mana pun Anda membutuhkan nilai hasil. Jangan lupa, janji hanya mewakili (proksi) nilai masa depan ! Di samping mendapatkan satu janji dari yang lain dalam rantai linier, gunakan combinator janji yang diberikan kepada Anda oleh perpustakaan Anda untuk membangun nilai hasil.

Ini akan menghasilkan aliran kontrol yang sangat mudah, komposisi fungsi yang jelas, dan oleh karenanya modularisasi mudah.

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

Alih-alih destructuring parameter di panggil balik setelah Promise.allhanya menjadi tersedia dengan ES6, di ES5 yang thenpanggilan akan digantikan oleh metode pembantu bagus yang disediakan oleh banyak perpustakaan janji ( Q , Bluebird , ketika , ...): .spread(function(resultA, resultB) { ….

Bluebird juga memiliki joinfungsi khusus untuk menggantikan kombinasi Promise.all+ itu spreaddengan konstruk yang lebih sederhana (dan lebih efisien):


return Promise.join(a, b, function(resultA, resultB) {  });
Bergi
sumber
1
Apakah fungsi di dalam array dijalankan secara berurutan?
scaryguy
6
@scaryguy: Tidak ada fungsi dalam array, itu adalah janji. promiseAdan promiseBapakah fungsi (janji kembali) di sini.
Bergi
2
@Roland Never berkata :-) Jawaban ini ditulis pada usia ES5 di mana tidak ada janji sama sekali dalam standar, dan spreadsangat berguna dalam pola ini. Untuk solusi yang lebih modern lihat jawaban yang diterima. Namun, saya sudah memperbarui jawaban passthrough eksplisit , dan benar-benar tidak ada alasan untuk tidak memperbarui yang satu ini juga.
Bergi
1
@reify Tidak, Anda tidak harus melakukan itu , itu akan membawa masalah dengan penolakan.
Bergi
1
Saya tidak mengerti contoh ini. Jika ada rantai pernyataan 'lalu' yang mengharuskan nilai-nilai disebarkan ke seluruh rantai, saya tidak melihat bagaimana ini menyelesaikan masalah. Janji yang membutuhkan nilai sebelumnya TIDAK DAPAT dipecat (dibuat) sampai nilai itu ada. Selain itu, Promise.all () hanya menunggu semua janji dalam daftar selesai: ia tidak memaksakan pesanan. Jadi saya membutuhkan setiap fungsi 'berikutnya' untuk memiliki akses ke semua nilai sebelumnya dan saya tidak melihat bagaimana contoh Anda melakukannya. Anda harus membimbing kami melalui teladan Anda, karena saya tidak percaya atau memahaminya.
David Spector
238

Harmony ECMAScript

Tentu saja, masalah ini juga diakui oleh perancang bahasa. Mereka melakukan banyak pekerjaan dan proposal fungsi async akhirnya berhasil

ECMAScript 8

Anda tidak memerlukan satu thenfungsi pemanggilan atau panggilan balik lagi, seperti pada fungsi asinkron (yang mengembalikan janji ketika dipanggil), Anda cukup menunggu janji untuk diselesaikan secara langsung. Ini juga menampilkan struktur kontrol sewenang-wenang seperti kondisi, loop dan klausa coba-tangkap, tetapi demi kenyamanan kita tidak membutuhkannya di sini:

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

ECMAScript 6

Sementara kami menunggu ES8, kami sudah menggunakan sintaks yang sangat mirip. ES6 hadir dengan fungsi generator , yang memungkinkan untuk memecah eksekusi menjadi beberapa bagian dengan yieldkata kunci yang ditempatkan secara sewenang-wenang . Irisan tersebut dapat dijalankan satu sama lain, secara mandiri, bahkan secara tidak sinkron - dan itulah yang kami lakukan ketika kami ingin menunggu resolusi yang dijanjikan sebelum menjalankan langkah berikutnya.

Ada perpustakaan khusus (seperti co atau task.js ), tetapi juga banyak perpustakaan janji memiliki fungsi pembantu ( Q , Bluebird , kapan , ...) yang melakukan eksekusi langkah-demi-langkah async ini untuk Anda ketika Anda memberi mereka fungsi generator yang menghasilkan janji.

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

Ini berhasil di Node.js sejak versi 4.0, juga beberapa browser (atau edisi dev mereka) mendukung sintaks generator relatif lebih awal.

ECMAScript 5

Namun, jika Anda ingin / perlu kompatibel-mundur Anda tidak dapat menggunakannya tanpa transpiler. Baik fungsi generator dan fungsi async didukung oleh tooling saat ini, lihat misalnya dokumentasi Babel pada generator dan fungsi async .

Dan kemudian, ada juga banyak bahasa kompilasi-ke-JS yang didedikasikan untuk memudahkan pemrograman asinkron. Mereka biasanya menggunakan sintaks mirip dengan await, (misalnya Iced CoffeeScript ), tetapi ada juga orang lain yang menampilkan Haskell-seperti do-notation (misalnya LatteJs , monadic , PureScript atau LispyScript ).

Bergi
sumber
@Bergi Anda perlu menunggu fungsi async memeriksa getExample () dari kode luar?
arisalexis
@arisalexis: Ya, getExamplemasih merupakan fungsi yang mengembalikan janji, berfungsi seperti fungsi di jawaban lain, tetapi dengan sintaks yang lebih bagus. Anda dapat awaitpanggilan dalam asyncfungsi lain , atau Anda dapat .then()membuat hasilnya.
Bergi
1
Saya ingin tahu, mengapa Anda menjawab pertanyaan Anda sendiri segera setelah mengajukannya? Ada beberapa diskusi yang bagus di sini, tapi saya penasaran. Mungkin Anda menemukan jawaban Anda sendiri setelah bertanya?
granmoe
@ granmoe: Saya memposting seluruh diskusi dengan sengaja sebagai target duplikat kanonik
Bergi
Apakah ada (tidak terlalu melelahkan) cara untuk menghindari menggunakan Promise.coroutine (yaitu, tidak menggunakan Bluebird atau perpustakaan lain, tetapi hanya JS biasa) dalam contoh ECMAScript 6 dengan fungsi generator? Saya ada dalam pikiran sesuatu steps.next().value.then(steps.next)...tetapi itu tidak berhasil.
seorang pelajar tidak memiliki nama
102

Inspeksi sinkron

Menetapkan nilai-nilai yang dijanjikan untuk variabel di kemudian hari dan kemudian mendapatkan nilainya melalui inspeksi sinkron. Contohnya menggunakan .value()metode bluebird tetapi banyak perpustakaan menyediakan metode serupa.

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

Ini dapat digunakan untuk nilai sebanyak yang Anda suka:

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
sumber
6
Ini adalah jawaban favorit saya: dapat dibaca, dapat dikembangkan, dan tidak terlalu bergantung pada fitur perpustakaan atau bahasa
Jason
13
@Jason: Uh, " ketergantungan minimal pada fitur perpustakaan "? Inspeksi sinkron adalah fitur perpustakaan, dan yang cukup non-standar untuk boot.
Bergi
2
Saya pikir maksudnya adalah fitur khusus perpustakaan
deathgaze
54

Penutupan bersarang (dan)

Menggunakan penutupan untuk mempertahankan ruang lingkup variabel (dalam kasus kami, parameter fungsi panggilan balik sukses) adalah solusi JavaScript alami. Dengan janji-janji, kita dapat secara sewenang-wenang mengumpulkan dan meratakan .then() panggilan balik - mereka secara semantik setara, kecuali untuk ruang lingkup bagian dalam.

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

Tentu saja, ini membangun lekukan piramida. Jika lekukan menjadi terlalu besar, Anda masih dapat menerapkan alat-alat lama untuk melawan piramida malapetaka : modularisasi, gunakan fungsi tambahan bernama, dan ratakan rantai janji segera setelah Anda tidak memerlukan variabel lagi.
Secara teori, Anda selalu dapat menghindari lebih dari dua tingkat bersarang (dengan membuat semua penutupan eksplisit), dalam praktiknya gunakan sebanyak yang masuk akal.

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

Anda juga dapat menggunakan fungsi pembantu untuk aplikasi parsial semacam ini , seperti _.partialdari Garis Bawah / lodash atau metode asli.bind() , untuk lebih jauh mengurangi indentasi:

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
sumber
5
Saran yang sama ini diberikan sebagai solusi untuk 'Kesalahan tingkat lanjut # 4' dalam artikel Nolan Lawson tentang janji pouchdb.com/2015/05/18/kami-memiliki-problem-dengan-promises.html . Itu bacaan yang bagus.
Robert
2
Ini persis bindfungsi di Monads. Haskell menyediakan sintaksis gula (jangan-notasi) agar terlihat seperti sintaksis async / tunggu.
zeronone
50

Pass-through eksplisit

Mirip dengan bersarangnya callback, teknik ini bergantung pada penutupan. Namun, rantai tetap datar - alih-alih hanya melewati hasil terbaru, beberapa objek negara dilewatkan untuk setiap langkah. Objek keadaan ini mengakumulasikan hasil dari tindakan sebelumnya, menyerahkan semua nilai yang akan dibutuhkan nanti ditambah hasil dari tugas saat ini.

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

Di sini, panah kecil itu b => [resultA, b]adalah fungsi yang menutup resultA, dan meneruskan array dari kedua hasil ke langkah berikutnya. Yang menggunakan sintaksis perusak parameter untuk memecahnya dalam variabel tunggal lagi.

Sebelum penghancuran tersedia dengan ES6, metode pembantu yang bagus disebut .spread()disediakan oleh banyak perpustakaan janji ( Q , Bluebird , kapan , ...). Dibutuhkan fungsi dengan beberapa parameter - satu untuk setiap elemen array - untuk digunakan sebagai .spread(function(resultA, resultB) { ….

Tentu saja, penutupan yang diperlukan di sini dapat lebih disederhanakan oleh beberapa fungsi pembantu, misalnya

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

Atau, Anda dapat menggunakan Promise.alluntuk menghasilkan janji untuk array:

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

Dan Anda mungkin tidak hanya menggunakan array, tetapi objek kompleks yang sewenang-wenang. Misalnya, dengan _.extendatau Object.assigndalam fungsi pembantu yang berbeda:

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

Sementara pola ini menjamin rantai datar dan objek keadaan eksplisit dapat meningkatkan kejelasan, itu akan menjadi membosankan untuk rantai panjang. Terutama ketika Anda membutuhkan negara hanya secara sporadis, Anda masih harus melewati setiap langkah. Dengan antarmuka tetap ini, panggilan balik tunggal dalam rantai agak erat digabungkan dan tidak fleksibel untuk diubah. Itu membuat anjak keluar satu langkah lebih sulit, dan callback tidak dapat dipasok langsung dari modul lain - mereka selalu harus dibungkus dengan kode boilerplate yang peduli dengan keadaan. Fungsi pembantu abstrak seperti di atas dapat sedikit meredakan rasa sakit, tetapi akan selalu ada.

Bergi
sumber
Pertama, saya tidak berpikir sintaks menghilangkan Promise.allseharusnya didorong (itu tidak akan bekerja di ES6 ketika merusak akan menggantikannya dan beralih .spreadke a thenmemberi orang hasil yang sering tak terduga. Pada penambahan - saya tidak yakin mengapa Anda perlu menggunakan augment - menambahkan hal-hal pada prototipe janji bukanlah cara yang dapat diterima untuk memperpanjang janji ES6 yang seharusnya diperpanjang dengan subklas (yang saat ini tidak didukung)
Benjamin Gruenbaum
@BenjaminGruenbaum: Apa yang Anda maksud dengan " menghilangkan sintaksisPromise.all "? Tidak ada metode dalam jawaban ini yang akan putus dengan ES6. Mengalihkan spreadke merusak thenharus tidak memiliki masalah juga. Re .prototype.augment: Saya tahu seseorang akan memperhatikannya, saya hanya suka menjelajahi kemungkinan - akan mengeditnya.
Bergi
Dengan sintaks array yang saya maksud return [x,y]; }).spread(...bukannya return Promise.all([x, y]); }).spread(...yang tidak akan berubah ketika bertukar menyebar untuk es6 merusak gula dan juga tidak akan menjadi kasus tepi aneh di mana janji memperlakukan kembali array berbeda dari yang lain.
Benjamin Gruenbaum
3
Ini mungkin jawaban terbaik. Janji adalah "Pemrograman Fungsional Reaktif", dan ini sering merupakan solusi yang digunakan. Sebagai contoh, BaconJs, memiliki #combineTemplate yang memungkinkan Anda untuk menggabungkan hasil menjadi objek yang diturunkan rantai
U Avalos
1
@CapiEtheriel Jawabannya ditulis ketika ES6 tidak selebar hari ini. Ya, mungkin sudah waktunya untuk bertukar contoh
Bergi
35

Keadaan kontekstual yang dapat berubah

Solusi trivial (tetapi tidak tepat dan agak error) adalah dengan hanya menggunakan variabel lingkup yang lebih tinggi (yang semua akses balik dalam rantai memiliki akses) dan menulis nilai hasil kepada mereka ketika Anda mendapatkannya:

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

Alih-alih banyak variabel orang mungkin juga menggunakan objek (awalnya kosong), di mana hasilnya disimpan sebagai properti yang dibuat secara dinamis.

Solusi ini memiliki beberapa kelemahan:

  • Keadaan yang bisa berubah itu jelek , dan variabel global itu jahat .
  • Pola ini tidak bekerja melintasi batas fungsi, memodulasi fungsi lebih sulit karena deklarasi mereka tidak boleh meninggalkan ruang lingkup bersama
  • Ruang lingkup variabel tidak mencegah untuk mengaksesnya sebelum mereka diinisialisasi. Ini sangat mungkin untuk konstruksi janji yang kompleks (loop, branching, excptions) di mana kondisi balapan mungkin terjadi. Melewati keadaan secara eksplisit, desain deklaratif yang menjanjikan dorongan, memaksa gaya pengkodean yang lebih bersih yang dapat mencegah hal ini.
  • Seseorang harus memilih ruang lingkup untuk variabel-variabel bersama dengan benar. Itu harus lokal ke fungsi yang dieksekusi untuk mencegah kondisi balapan antara beberapa doa paralel, seperti halnya jika, misalnya, negara disimpan pada instance.

Perpustakaan Bluebird mendorong penggunaan objek yang diteruskan, menggunakan metode merekabind() untuk menetapkan objek konteks ke rantai janji. Ini akan dapat diakses dari setiap fungsi panggilan balik melalui thiskata kunci yang tidak dapat digunakan . Sementara properti objek lebih rentan terhadap kesalahan ketik yang tidak terdeteksi daripada variabel, polanya cukup pintar:

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
}

Pendekatan ini dapat dengan mudah disimulasikan di perpustakaan janji yang tidak mendukung .bind (meskipun dengan cara yang agak lebih bertele-tele dan tidak dapat digunakan dalam ekspresi):

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
sumber
.bind()tidak perlu untuk mencegah kebocoran memori
Esailija
@Esailija: Tapi bukankah janji yang dikembalikan memiliki referensi ke objek konteks? OK, tentu saja pengumpulan sampah akan menanganinya nanti; itu bukan "kebocoran" kecuali janjinya tidak pernah dibuang.
Bergi
Ya tapi janji-janji juga memegang referensi untuk nilai pemenuhan mereka dan alasan kesalahan ... tapi tidak ada yang mengacu pada janji itu jadi tidak masalah
Esailija
4
Tolong pecahkan jawaban ini menjadi dua karena saya hampir memilih pembukaan. Saya pikir "solusi yang sepele (tetapi tidak tepat dan agak error)" adalah solusi yang paling bersih dan paling sederhana, karena itu tidak lagi bergantung pada penutupan dan keadaan yang bisa berubah daripada jawaban yang Anda terima sendiri, namun lebih sederhana. Penutupan bukanlah global atau jahat. Argumen yang diberikan terhadap pendekatan ini tidak masuk akal bagi saya mengingat premis. Apa masalah modularisasi yang bisa diberikan "rantai janji datar panjang yang indah"?
jib
2
Seperti yang saya katakan di atas, Janji adalah "Fungsional Pemrograman Reaktif". Ini adalah anti-pola dalam FRP
U Avalos
15

Putaran yang kurang keras pada "Keadaan kontekstual yang dapat berubah"

Menggunakan objek yang dicakup secara lokal untuk mengumpulkan hasil antara dalam rantai janji adalah pendekatan yang masuk akal untuk pertanyaan yang Anda ajukan. Pertimbangkan cuplikan berikut:

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);
    });
}
  • Variabel global buruk, sehingga solusi ini menggunakan variabel cakupan lokal yang tidak menyebabkan kerusakan. Ini hanya dapat diakses dalam fungsi.
  • Keadaan yang bisa berubah itu jelek, tetapi ini tidak mengubah keadaan dengan cara yang buruk. Keadaan jelek yang bisa berubah secara tradisional mengacu pada memodifikasi keadaan argumen fungsi atau variabel global, tetapi pendekatan ini hanya memodifikasi keadaan variabel cakupan lokal yang ada untuk tujuan agregasi hasil janji ... variabel yang akan mati sederhana mati setelah janji terselesaikan.
  • Janji menengah tidak dicegah untuk mengakses keadaan objek hasil, tetapi ini tidak memperkenalkan beberapa skenario menakutkan di mana salah satu janji dalam rantai akan basi dan menyabot hasil Anda. Tanggung jawab pengaturan nilai dalam setiap langkah dari janji terbatas pada fungsi ini dan hasil keseluruhan akan benar atau salah ... itu tidak akan menjadi bug yang akan muncul bertahun-tahun kemudian dalam produksi (kecuali jika Anda bermaksud untuk !)
  • Ini tidak memperkenalkan skenario kondisi balapan yang akan muncul dari pemanggilan paralel karena instance baru dari variabel hasil dibuat untuk setiap pemanggilan fungsi getExample.
Jay
sumber
1
Setidaknya hindari Promiseantipattern konstruktor !
Bergi
Terima kasih @Bergi, saya bahkan tidak menyadari itu anti-pola sampai Anda menyebutkannya!
Jay
ini adalah solusi yang baik untuk mengurangi kesalahan terkait janji. Saya menggunakan ES5 dan tidak ingin menambahkan perpustakaan lain untuk bekerja dengan janji.
nilakantha singh deo
8

Node 7.4 sekarang mendukung panggilan async / menunggu dengan bendera harmoni.

Coba ini:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

dan jalankan file dengan:

node --harmony-async-await getExample.js

Sesederhana mungkin!

Anthony
sumber
8

Hari ini, saya juga harus memenuhi beberapa pertanyaan seperti Anda. Akhirnya, saya menemukan solusi yang baik dengan pertanyaan itu, sederhana dan bagus untuk dibaca. Saya harap ini dapat membantu Anda.

Menurut cara-untuk-rantai-javascript-janji

ok, mari kita lihat kodenya:

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
sumber
4
Ini tidak benar-benar menjawab pertanyaan tentang cara mengakses hasil sebelumnya dalam rantai.
Bergi
2
Setiap janji bisa mendapatkan nilai sebelumnya, apa artinya Anda?
yzfdjzwl
1
Lihatlah kode dalam pertanyaan. Tujuannya bukan untuk mendapatkan hasil dari janji yang .thendipanggil, tetapi hasil dari sebelumnya. Misalnya thirdPromisemengakses hasil dari firstPromise.
Bergi
6

Jawaban lain, menggunakan babel-node versi <6

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

Lalu, jalankan babel-node example.jsdan voila!

Anthony
sumber
1
Ya saya lakukan, tepat setelah saya memposting milik saya. Namun, saya akan meninggalkannya karena ini menjelaskan bagaimana cara benar-benar bangun dan berjalan dengan menggunakan ES7 yang bertentangan dengan hanya mengatakan bahwa suatu hari ES7 akan tersedia.
Anthony
1
Oh benar, saya harus memperbarui jawaban saya untuk mengatakan bahwa plugin "eksperimental" untuk ini sudah ada di sini.
Bergi
2

Saya tidak akan menggunakan pola ini dalam kode saya sendiri karena saya bukan penggemar menggunakan variabel global. Namun, dalam keadaan darurat itu akan berhasil.

Pengguna adalah model luwak resmi.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
Anthony
sumber
2
Perhatikan bahwa pola ini sudah dirinci dalam jawaban status kontekstual Mutable (dan juga mengapa jelek - saya juga bukan penggemar berat)
Bergi
Dalam kasus Anda, polanya tampaknya tidak berguna. Anda tidak perlu globalVarsama sekali, lakukan saja User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi
1
Saya tidak membutuhkannya secara pribadi dalam kode saya sendiri, tetapi pengguna mungkin perlu menjalankan lebih banyak async pada fungsi kedua dan kemudian berinteraksi dengan panggilan Janji asli. Tapi seperti yang disebutkan, saya akan menggunakan generator dalam hal ini. :)
Anthony
2

Jawaban lain, menggunakan nsynjs pelaksana berurutan :

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

Pembaruan: menambahkan contoh kerja

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
sumber
1

Saat menggunakan bluebird, Anda dapat menggunakan .bindmetode untuk berbagi variabel dalam rantai janji:

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

silakan periksa tautan ini untuk informasi lebih lanjut:

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

alphakevin
sumber
Perhatikan bahwa pola ini sudah dirinci dalam jawaban status kontekstual
Mutable
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?
    });
}

cara mudah: D

Minh Giang
sumber
Anda memperhatikan jawaban ini ?
Bergi
1

Saya pikir Anda dapat menggunakan hash RSVP.

Sesuatu seperti di bawah ini:

    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);
    });
Wisnu
sumber
Ya, itu sama dengan Promise.allsolusinya , hanya dengan objek bukan array.
Bergi
0

Larutan:

Anda dapat menempatkan nilai-nilai perantara dalam lingkup fungsi nanti 'lalu' secara eksplisit, dengan menggunakan 'bind'. Ini adalah solusi bagus yang tidak perlu mengubah cara kerja Janji, dan hanya membutuhkan satu atau dua baris kode untuk menyebarkan nilai-nilai seperti halnya kesalahan sudah diperbanyak.

Ini adalah contoh lengkapnya:

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

Solusi ini dapat dipanggil sebagai berikut:

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

(Catatan: versi yang lebih kompleks dan lengkap dari solusi ini telah diuji, tetapi bukan versi contoh ini, sehingga dapat memiliki bug.)

David Spector
sumber
Ini tampaknya merupakan pola yang sama dengan jawaban penutupan bersarang (dan)
Bergi
Itu memang terlihat mirip. Sejak itu saya mengetahui bahwa sintaks Async / Await yang baru mencakup pengikatan argumen secara otomatis, sehingga semua argumen tersedia untuk semua fungsi asinkron. Saya meninggalkan Janji.
David Spector
asyncSaya awaitmasih berarti menggunakan janji. Yang mungkin Anda tinggalkan adalah thenpanggilan dengan panggilan balik.
Bergi
-1

Apa yang saya pelajari tentang janji adalah menggunakannya hanya sebagai nilai pengembalian menghindari merujuk jika memungkinkan. sintaks async / await sangat praktis untuk itu. Hari ini semua browser dan node terbaru mendukungnya: https://caniuse.com/#feat=async-functions , adalah perilaku sederhana dan kodenya seperti membaca kode sinkron, lupakan panggilan balik ...

Dalam kasus yang saya perlu rujuk janji adalah ketika penciptaan dan resolusi terjadi di tempat-tempat independen / tidak terkait. Jadi daripada asosiasi buatan dan mungkin pendengar acara hanya untuk menyelesaikan janji "jauh", saya lebih suka mengekspos janji sebagai Ditangguhkan, yang diterapkan oleh kode berikut ini dalam es5 yang valid

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

diubah bentuk dari proyek naskah saya:

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

Untuk kasus yang lebih kompleks saya sering menggunakan utilitas janji orang kecil ini tanpa dependensi diuji dan diketik. p-map telah berguna beberapa kali. Saya pikir dia membahas sebagian besar kasus penggunaan:

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

kankerbero
sumber
Kedengarannya seperti Anda menyarankan keadaan kontekstual yang dapat berubah atau inspeksi sinkron ?
Bergi
@bergi Pertama kali saya mengepalai nama-nama itu. menambahkan daftar terima kasih. Saya tahu jenis janji sadar diri ini dengan nama Ditangguhkan - BTW pelaksanaannya hanyalah sebuah janji dengan tekad terbungkus. Saya sering membutuhkan pola ini dalam kasus-kasus di mana tanggung jawab penciptaan janji dan resolusi independen sehingga tidak perlu menghubungkannya hanya untuk menyelesaikan janji. Saya beradaptasi tetapi tidak untuk contoh Anda, dan menggunakan kelas, tapi mungkin setara.
cancerbero