Bagaimana Anda mengembalikan beberapa nilai dengan benar dari sebuah promise?


87

Saya baru-baru ini mengalami situasi tertentu beberapa kali, yang tidak saya ketahui cara menyelesaikannya dengan benar. Asumsikan kode berikut:

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

Sekarang situasi mungkin muncul di mana saya ingin memiliki akses amazingDatamasuk afterSomethingElse.

Salah satu solusi yang jelas adalah mengembalikan array atau hash dari afterSomething, karena, Anda hanya dapat mengembalikan satu nilai dari suatu fungsi. Tetapi saya bertanya-tanya apakah ada cara untuk afterSomethingElsemenerima 2 parameter dan menjalankannya juga, karena itu tampaknya jauh lebih mudah untuk didokumentasikan dan dipahami.

Saya hanya bertanya-tanya tentang kemungkinan ini karena ada Q.spread, yang melakukan sesuatu yang mirip dengan apa yang saya inginkan.




De-structuring Assignment di ES6 akan membantu. periksa di sini
Ravi Teja

Jawaban:


88

Anda tidak dapat menyelesaikan suatu janji dengan beberapa properti seperti halnya Anda tidak dapat mengembalikan beberapa nilai dari suatu fungsi . Promise secara konseptual merepresentasikan nilai dari waktu ke waktu, jadi meskipun Anda bisa merepresentasikan nilai komposit, Anda tidak bisa memasukkan beberapa nilai dalam sebuah promise.

Promise secara inheren diselesaikan dengan satu nilai - ini adalah bagian dari cara kerja Q, cara kerja spesifikasi Promises / A +, dan cara kerja abstraksi .

Yang paling dekat yang bisa Anda dapatkan adalah menggunakan Q.spreaddan mengembalikan array atau menggunakan ES6 destructuring jika didukung atau Anda ingin menggunakan alat transpilasi seperti BabelJS.

Adapun untuk meneruskan konteks ke rantai janji, silakan lihat kanonik Bergi yang sangat baik tentang itu .


16
Apa salahnya menyelesaikan dengan objek yang memiliki banyak properti? Sepertinya cara sederhana untuk mendapatkan banyak nilai dari sebuah keputusan.
jfriend00

6
Tidak masalah melakukan itu
Benjamin Gruenbaum

Anda juga dapat memperpanjang Janji untuk .spread()menampilkan Bluebird seperti ini di jawaban terkait ini: stackoverflow.com/a/22776850/1624862
Kevin Ghadyani

Perilaku Promise.all () tampaknya bertentangan dengan ini. Promise.all([a, b, c]).then(function(x, y, z) {...})bekerja dengan benar di semua mesin Javascript modern dengan x, y, dan z mengevaluasi ke nilai yang diselesaikan dari a, b, dan c. Jadi lebih akurat untuk mengatakan bahwa bahasa tidak membiarkan Anda melakukannya dengan mudah (atau masuk akal) dari kode pengguna (karena Anda dapat mengembalikan Promise langsung dari klausa then, Anda dapat menggabungkan nilai Anda dalam promise dan kemudian menggabungkannya dengan Promise .all () untuk mendapatkan perilaku yang diinginkan, meskipun dengan cara yang berbelit-belit).
Austin Hemmelgarn

3
@AustinHemmelgarn Itu hanya salah, Promise.alldipenuhi dengan array . Dalam Promise.all([a,b]).then((a, b) => badalah undefined. Itulah mengapa Anda perlu melakukan .then(([a, b]) =>yang merupakan tugas yang merusak
Benjamin Gruenbaum

39

Anda hanya dapat mengirimkan satu nilai, tetapi dapat berupa larik dengan nilai kelipatan di dalamnya, sebagai contoh:

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

di sisi lain, Anda dapat menggunakan ekspresi penghancuran untuk ES2015 untuk mendapatkan nilai individual.

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

untuk menyebut keduanya janji, merangkainya:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})

5
Ini disebut destructuring , bukan "deconstructor", dan ini bukan operator: - /
Bergi

3
Btw, Anda dapat menggunakan hak penghancuran di parameter: function step2([server, data]) { …- dengan cara itu Anda juga menghindari untuk menetapkan ke global implisit. Dan Anda benar-benar harus menggunakan returnatau Promise.resolve, bukan new Promisekonstruktor dalam contoh Anda.
Bergi

terima kasih @Bergi untuk rekomendasinya!
Alejandro Silva

20

Anda dapat mengembalikan objek yang berisi kedua nilai - tidak ada yang salah dengan itu.

Strategi lain adalah mempertahankan nilai, melalui closure, alih-alih meneruskannya:

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

Bentuk sebaris sepenuhnya daripada sebagian (setara, bisa dibilang lebih konsisten):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

3
Tidak apa-apa untuk mengembalikan bagian thendalam yang lain then? Ini bukan anti-pola ?
robe007

seperti yang dikatakan @ robe007, bukankah ini mirip dengan 'callback hell'? di sini bersarang Anda kemudian memblokir alih-alih fungsi panggilan balik, ini akan mengalahkan tujuan dari memiliki janji
Dheeraj

5

Dua hal yang dapat Anda lakukan, mengembalikan sebuah benda

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

Gunakan ruang lingkup!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}

3

Menurut saya, inilah yang harus Anda lakukan.

memisahkan rantai

Karena kedua fungsi akan menggunakan amazingData , maka masuk akal untuk memilikinya dalam fungsi khusus. Saya biasanya melakukan itu setiap kali saya ingin menggunakan kembali beberapa data, jadi itu selalu ada sebagai fungsi arg.

Sebagai contoh Anda menjalankan beberapa kode, saya anggap semuanya dideklarasikan di dalam sebuah fungsi. Saya akan menyebutnya toto () . Kemudian kita akan memiliki fungsi lain yang akan menjalankan afterSomething () dan afterSomethingElse () .

function toto() {
    return somethingAsync()
        .then( tata );
}

Anda juga akan melihat saya menambahkan pernyataan pengembalian karena biasanya ini adalah cara untuk mengikuti Promises - Anda selalu mengembalikan janji sehingga kami dapat terus merangkai jika diperlukan. Di sini, sesuatuAsync () akan menghasilkan amazingData dan akan tersedia di mana saja di dalam fungsi baru.

Sekarang, seperti apa fungsi baru ini yang biasanya bergantung pada prosesAsync () juga asynchronous ?

processAsync tidak asynchronous

Tidak ada alasan untuk memperumit masalah jika processAsync () tidak asynchronous. Beberapa kode sekuensial lama yang bagus akan berhasil.

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

Perhatikan bahwa tidak masalah jika afterSomethingElse () melakukan sesuatu yang asinkron atau tidak. Jika ya, janji akan dikembalikan dan rantai dapat berlanjut. Jika tidak, maka nilai hasil akan dikembalikan. Tetapi karena fungsinya dipanggil dari a then () , nilainya akan tetap digabungkan menjadi janji (setidaknya dalam Javascript mentah).

processAsync asynchronous

Jika processAsync () asynchronous, kode akan terlihat sedikit berbeda. Di sini kami menganggap afterSomething () dan afterSomethingElse () tidak akan digunakan kembali di tempat lain.

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

Sama seperti sebelumnya untuk afterSomethingElse () . Ini bisa asinkron atau tidak. Sebuah janji akan dikembalikan, atau nilai yang dibungkus menjadi janji yang diselesaikan.


Gaya pengkodean Anda cukup dekat dengan apa yang biasa saya lakukan, itulah mengapa saya menjawab bahkan setelah 2 tahun. Saya bukan penggemar berat fungsi anonim di mana-mana. Saya merasa sulit untuk membaca. Kalaupun hal itu cukup umum di masyarakat. Seperti kita mengganti neraka panggilan dengan api penyucian janji .

Saya juga suka membuat nama fungsi di kemudian pendek. Mereka hanya akan didefinisikan secara lokal. Dan sebagian besar waktu mereka akan memanggil fungsi lain yang ditentukan di tempat lain - sangat dapat digunakan kembali - untuk melakukan pekerjaan itu. Saya bahkan melakukan itu untuk fungsi dengan hanya 1 parameter, jadi saya tidak perlu memasukkan fungsi masuk dan keluar saat saya menambahkan / menghapus parameter ke tanda tangan fungsi.

Contoh makan

Berikut ini contohnya:

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

Jangan terlalu fokus pada Promise.resolve () . Ini hanyalah cara cepat untuk membuat janji yang sudah pasti. Apa yang saya mencoba untuk mencapai dengan ini adalah untuk memiliki semua kode saya berjalan di satu lokasi - hanya di bawah thens . Semua fungsi lainnya dengan nama yang lebih deskriptif dapat digunakan kembali.

Kelemahan dari teknik ini adalah teknik ini mendefinisikan banyak fungsi. Tapi itu rasa sakit yang perlu saya khawatirkan untuk menghindari fungsi anonim di mana-mana. Dan apa risikonya: tumpukan melimpah? (lelucon!)


Menggunakan array atau objek seperti yang didefinisikan dalam jawaban lain juga akan berfungsi. Yang satu ini adalah jawaban yang diajukan oleh Kevin Reid .

Anda juga dapat menggunakan bind () atau Promise.all () . Perhatikan bahwa mereka masih akan meminta Anda untuk membagi kode Anda.

menggunakan bind

Jika Anda ingin agar fungsi Anda dapat digunakan kembali tetapi tidak benar-benar perlu menyimpan apa yang ada di dalamnya dalam waktu yang sangat singkat, Anda dapat menggunakan bind () .

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

Sederhananya, bind () akan menambahkan daftar args (kecuali yang pertama) ke fungsi saat dipanggil.

menggunakan Promise.all

Di postingan Anda, Anda menyebutkan penggunaan spread () . Saya tidak pernah menggunakan kerangka kerja yang Anda gunakan, tetapi inilah cara Anda dapat menggunakannya.

Beberapa percaya Promise.all () adalah solusi untuk semua masalah, jadi saya rasa pantas untuk disebutkan.

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

Anda bisa meneruskan data ke Promise.all () - perhatikan keberadaan array - selama promise, tetapi pastikan tidak ada promise yang gagal jika tidak akan berhenti diproses.

Dan alih-alih menentukan variabel baru dari argumen args , Anda harus bisa menggunakan spread () daripada then () untuk semua jenis pekerjaan yang mengagumkan.


3

Cukup buat sebuah objek dan ekstrak argumen dari objek tersebut.

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

Tarik argumen dari promiseResolution.

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});

2

Apa pun yang Anda hasilkan dari sebuah janji akan dibungkus menjadi janji untuk dibuka pada .then()tahap berikutnya .

Ini menjadi menarik saat Anda perlu mengembalikan satu atau beberapa promise di samping satu atau beberapa nilai sinkron seperti;

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

Dalam kasus ini, penting untuk menggunakan Promise.all()untuk mendapatkan p1dan p2membuka bungkusan janji pada .then()tahap berikutnya seperti

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);

1

Anda dapat memeriksa Observable yang diwakili oleh Rxjs , memungkinkan Anda mengembalikan lebih dari satu nilai.


0

Cukup kembalikan tupel:

async add(dto: TDto): Promise<TDto> {
console.log(`${this.storeName}.add(${dto})`);
return firebase.firestore().collection(this.dtoName)
  .withConverter<TDto>(this.converter)
  .add(dto)
  .then(d => [d.update(this.id, d.id), d.id] as [any, string])
  .then(x => this.get(x[1]));

}

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.