Apakah antipattern konstruksi janji eksplisit dan bagaimana cara menghindarinya?

516

Saya sedang menulis kode yang melakukan sesuatu yang terlihat seperti:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

Seseorang mengatakan kepada saya bahwa ini disebut " antipattern ditangguhkan " atau " Promiseantipattern konstruktor " masing-masing, apa yang buruk tentang kode ini dan mengapa ini disebut antipattern ?

Benjamin Gruenbaum
sumber
Dapatkah saya mengkonfirmasi bahwa penghapusan ini adalah untuk (dalam konteks kanan, bukan kiri, contoh) menghapus getStuffDonepembungkus fungsi dan hanya menggunakan literal Janji?
The Dembinski
1
atau apakah memiliki catchblok di getStuffDonepembungkus antipattern?
The Dembinski
1
Setidaknya untuk Promisecontoh asli Anda juga memiliki pembungkus fungsi yang tidak perlu untuk .thendan .catchpenangan (yaitu bisa saja .then(resolve).catch(reject).) Badai sempurna anti-pola.
Noah Freitas
6
@NoahFreitas kode yang ditulis seperti itu untuk tujuan didaktik. Saya menulis pertanyaan dan jawaban ini untuk membantu orang yang mengalami masalah ini setelah membaca banyak kode seperti itu :)
Benjamin Gruenbaum
Lihat juga stackoverflow.com/questions/57661537/... untuk cara menghilangkan tidak hanya konstruksi Janji yang eksplisit, tetapi juga penggunaan variabel global.
David Spector

Jawaban:

357

The antipattern ditangguhkan (sekarang eksplisit-konstruksi antipattern) diciptakan oleh Esailija adalah orang antipattern umum yang baru untuk janji membuat, saya telah membuat sendiri ketika saya pertama kali digunakan janji. Masalah dengan kode di atas adalah bahwa gagal untuk memanfaatkan fakta yang menjanjikan rantai.

Janji dapat dirangkai dengan .thendan Anda dapat mengembalikan janji secara langsung. Kode Anda di getStuffDonedapat ditulis ulang sebagai:

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

Semua janji adalah tentang membuat kode asinkron lebih mudah dibaca dan berperilaku seperti kode sinkron tanpa menyembunyikan fakta itu. Janji merupakan abstraksi atas nilai satu kali operasi, mereka abstrak gagasan pernyataan atau ungkapan dalam bahasa pemrograman.

Anda hanya boleh menggunakan objek yang ditangguhkan saat Anda mengonversi API menjadi janji dan tidak bisa melakukannya secara otomatis, atau saat Anda menulis fungsi agregasi yang lebih mudah diungkapkan dengan cara ini.

Mengutip Esailija:

Ini adalah anti-pola yang paling umum. Sangat mudah untuk jatuh ke dalam ini ketika Anda tidak benar-benar memahami janji-janji dan menganggapnya sebagai pemancar acara yang dimuliakan atau utilitas panggilan balik. Mari kita rekap: janji adalah tentang membuat kode asinkron mempertahankan sebagian besar sifat yang hilang dari kode sinkron seperti indentasi datar dan satu saluran pengecualian.

Benjamin Gruenbaum
sumber
@BenjaminGruenbaum: Saya yakin dalam penggunaan saya atas penundaan untuk ini, jadi tidak perlu pertanyaan baru. Saya hanya berpikir itu adalah kasus penggunaan Anda hilang dari jawaban Anda. Apa yang saya lakukan sepertinya lebih merupakan kebalikan dari agregasi, bukan?
mhelvens
1
@mhelvens Jika Anda secara manual memisahkan API non-panggilan balik menjadi API janji yang sesuai dengan bagian "mengonversi API panggilan balik ke janji". Antipattern adalah tentang membungkus janji dengan janji lain tanpa alasan yang baik, Anda tidak membungkus janji untuk memulai sehingga tidak berlaku di sini.
Benjamin Gruenbaum
@BenjaminGruenbaum: Ah, saya pikir menunda sendiri dianggap anti-pola, apa dengan burung bluebird mencela mereka, dan Anda menyebutkan "mengubah API menjadi janji" (yang juga merupakan kasus tidak membungkus janji untuk memulai dengan).
mhelvens
@ mhelvens Saya kira kelebihan pola anti ditangguhkan akan lebih akurat untuk apa yang sebenarnya dilakukannya. Bluebird mencabut .defer()api ke dalam konstruktor janji yang lebih baru (dan melempar aman), itu tidak (sama sekali) tidak mencemari gagasan membangun janji :)
Benjamin Gruenbaum
1
Terima kasih @ Roamer-1888 referensi Anda akhirnya membantu saya mencari tahu apa masalah saya. Sepertinya saya membuat janji-janji bersarang (tidak dikembalikan) tanpa menyadarinya.
ghuroo
134

Apakah ada yang salah?

Tapi polanya berhasil!

Beruntunglah anda. Sayangnya, mungkin tidak, karena Anda mungkin lupa beberapa tepi case. Dalam lebih dari setengah kejadian yang saya lihat, penulis lupa untuk mengurus penangan kesalahan:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

Jika janji lain ditolak, ini akan terjadi tanpa disadari alih-alih disebarkan ke janji baru (di mana janji itu akan ditangani) - dan janji baru itu tetap tertunda selamanya, yang dapat menyebabkan kebocoran.

Hal yang sama terjadi jika kode panggilan balik Anda menyebabkan kesalahan - misalnya ketika resulttidak memiliki propertydan pengecualian dilemparkan. Itu tidak akan ditangani dan meninggalkan janji baru yang belum terselesaikan.

Sebaliknya, menggunakan .then()tidak secara otomatis menangani kedua skenario ini, dan menolak janji baru ketika terjadi kesalahan:

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

Antipattern yang ditangguhkan tidak hanya rumit, tetapi juga rawan kesalahan . Menggunakan .then()untuk chaining jauh lebih aman.

Tapi saya sudah menangani semuanya!

Betulkah? Baik. Namun, ini akan sangat rinci dan berlebihan, terutama jika Anda menggunakan perpustakaan janji yang mendukung fitur-fitur lain seperti pembatalan atau pengiriman pesan. Atau mungkin di masa depan, atau Anda ingin menukar perpustakaan Anda dengan yang lebih baik? Anda tidak akan ingin menulis ulang kode Anda untuk itu.

Metode perpustakaan ( then) tidak hanya mendukung semua fitur secara asli, mereka juga mungkin memiliki optimisasi tertentu. Menggunakannya kemungkinan akan membuat kode Anda lebih cepat, atau setidaknya memungkinkan untuk dioptimalkan oleh revisi perpustakaan di masa depan.

Bagaimana saya menghindarinya?

Jadi, setiap kali Anda menemukan diri Anda secara manual membuat Promiseatau Deferreddan sudah ada janji yang terlibat, periksa API perpustakaan terlebih dahulu . Antipattern yang ditangguhkan sering diterapkan oleh orang-orang yang melihat janji [hanya] sebagai pola pengamat - tetapi janji lebih dari sekadar panggilan balik : mereka seharusnya dapat dikomposasikan. Setiap perpustakaan yang layak memiliki banyak fungsi yang mudah digunakan untuk komposisi janji-janji dalam setiap cara yang dapat dipikirkan, mengurus semua hal tingkat rendah yang tidak ingin Anda tangani.

Jika Anda menemukan kebutuhan untuk menyusun beberapa janji dengan cara baru yang tidak didukung oleh fungsi pembantu yang ada, menulis fungsi Anda sendiri dengan Deferred yang tidak dapat dihindari harus menjadi pilihan terakhir Anda. Pertimbangkan beralih ke perpustakaan yang lebih berfitur, dan / atau ajukan bug pada perpustakaan Anda saat ini. Pemeliharanya harus dapat memperoleh komposisi dari fungsi yang ada, menerapkan fungsi pembantu baru untuk Anda dan / atau membantu mengidentifikasi kasus tepi yang perlu ditangani.

Bergi
sumber
Apakah ada contoh, selain fungsi termasuk setTimeout, di mana konstruktor dapat digunakan tetapi tidak dianggap "Janji anorpattern konstruktor"?
tamu271314
1
@ guest271314: Semuanya asinkron yang tidak memberikan janji. Meskipun cukup sering Anda mendapatkan hasil yang lebih baik dengan pembantu berdedikasi perpustakaan yang berdedikasi. Dan pastikan untuk selalu berjanji pada level terendah, jadi itu bukan " fungsi termasuksetTimeout ", tetapi " fungsi setTimeoutitu sendiri ".
Bergi
"Dan pastikan untuk selalu berjanji pada level terendah, jadi itu bukan" fungsi termasuk setTimeout", tapi" fungsi setTimeoutitu sendiri "" Bisakah menggambarkan, tautan ke perbedaan, di antara keduanya?
tamu271314
@ guest271314 Fungsi yang hanya menyertakan panggilan ke setTimeoutjelas berbeda dari fungsi setTimeoutitu sendiri , bukan?
Bergi
4
Saya pikir salah satu pelajaran penting di sini, salah satu yang belum jelas dinyatakan sejauh ini, adalah bahwa Janji dan dirantai 'maka' mewakili satu operasi asinkron: operasi awal adalah dalam konstruktor Janji dan titik akhir akhirnya ada di ' lalu berfungsi. Jadi, jika Anda memiliki operasi sinkronisasi yang diikuti oleh operasi asynch, masukkan hal-hal sinkronisasi di Janji. Jika Anda memiliki operasi async yang diikuti oleh sinkronisasi, masukkan hal-hal sinkronisasi di 'lalu'. Dalam kasus pertama, kembalikan Janji asli. Dalam kasus kedua, kembalikan rantai Janji / lalu (yang juga merupakan Janji).
David Spector