Kapan. Kemudian (sukses, gagal) dianggap sebagai antipattern untuk janji?

188

Saya telah melihat FAQ tentang burung bluebird , yang menyebutkan bahwa itu .then(success, fail)adalah antipattern . Saya tidak begitu mengerti penjelasannya tentang mencoba dan menangkap. Apa yang salah dengan yang berikut ini?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Tampaknya contoh ini menyarankan yang berikut ini sebagai cara yang benar.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Apa bedanya?

pengguna2127480
sumber
1
then().catch()lebih mudah dibaca, karena Anda tidak perlu mencari koma dan menyelidiki apakah panggilan balik ini untuk cabang sukses atau gagal.
Krzysztof Safjanowski
7
@ KevinB: Ada banyak perbedaan, periksa jawabannya
Bergi
12
@KrzysztofSafjanowski - hancur oleh argumen 'terlihat lebih baik'. Sangat salah!
Andrey Popov
6
CATATAN: Saat Anda menggunakan .catch, Anda tidak tahu langkah mana yang menyebabkan masalah - di dalam yang terakhir thenatau di tempat lain di rantai janji. Jadi memang memiliki kekurangannya sendiri.
vitaly-t
2
Saya selalu menambahkan nama fungsi ke janji. Maka () params untuk membuatnya dapat dibaca yaitusome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Jawaban:

215

Apa bedanya?

The .then()panggilan akan kembali janji yang akan ditolak dalam kasus panggil balik melempar kesalahan. Ini berarti, ketika kesuksesan Anda loggergagal, kesalahan akan diteruskan ke .catch()callback berikut , tetapi tidak ke failcallback yang menyertainya success.

Berikut diagram alir kontrol :

diagram alir kontrol kemudian dengan dua argumen diagram alir kontrol dari kemudian rantai tangkapan

Untuk mengekspresikannya dalam kode sinkron:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

Yang kedua log(yang seperti argumen pertama .then()) hanya akan dieksekusi jika tidak ada pengecualian yang terjadi. Blok berlabel dan breakpernyataan merasa sedikit aneh, ini sebenarnya apa python memiliki try-except-elseuntuk (membaca direkomendasikan!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

The catchlogger juga akan menangani pengecualian dari panggilan sukses logger.

Begitu banyak perbedaannya.

Saya tidak begitu mengerti penjelasannya tentang mencoba dan menangkap

Argumennya adalah bahwa biasanya Anda ingin menangkap kesalahan di setiap langkah pemrosesan, dan Anda tidak boleh menggunakannya dalam rantai. Harapannya adalah bahwa Anda hanya memiliki satu penangan akhir yang menangani semua kesalahan - sementara, ketika Anda menggunakan "antipattern", kesalahan dalam beberapa panggilan balik saat itu tidak ditangani.

Namun, pola ini sebenarnya sangat berguna: Ketika Anda ingin menangani kesalahan yang terjadi pada langkah ini, dan Anda ingin melakukan sesuatu yang sama sekali berbeda ketika tidak ada kesalahan terjadi - yaitu ketika kesalahan tidak dapat dipulihkan. Ketahuilah bahwa ini adalah percabangan aliran kendali Anda. Tentu saja, ini kadang diinginkan.


Apa yang salah dengan yang berikut ini?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Bahwa Anda harus mengulangi panggilan balik Anda. Anda lebih suka

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

Anda juga dapat mempertimbangkan .finally()untuk menggunakan ini.

Bergi
sumber
7
ini adalah penjelasan paling bermanfaat yang pernah saya baca dalam beberapa hari (dan saya sudah banyak membaca). Saya tidak bisa menjelaskan betapa bersyukurnya saya! :) Saya pikir Anda harus lebih menekankan perbedaan antara keduanya, yang .catchakan menangkap kesalahan bahkan di dalam fungsi sukses .. Secara pribadi, saya menemukan ini sangat salah karena Anda berakhir dengan satu titik masuk kesalahan, yang bisa mendapatkan banyak kesalahan dari beberapa tindakan, tapi ini masalah saya. Bagaimanapun - terima kasih untuk informasinya! Apakah Anda tidak memiliki alat komunikasi online yang ingin Anda bagikan sehingga saya dapat menanyakan beberapa hal lagi? : P
Andrey Popov
2
Saya harap ini memberi Anda lebih banyak upvotes di sini. Pasti salah satu penjelasan terbaik dari Promisemekanik penting di situs ini.
Patrick Roberts
2
.done()bukan bagian dari standar, kan? Setidaknya MDN tidak mencantumkan metode itu. Itu akan sangat membantu.
ygoe
1
@ygoe Memang. doneadalah hal Bluebird yang pada dasarnya usang oleh thendeteksi + unhandled-rejection.
Bergi
1
hanya sebuah catatan dari orang buta warna: diagram tidak masuk akal :)
Benny K
37

Keduanya tidak identik. Perbedaannya adalah bahwa contoh pertama tidak akan menangkap pengecualian yang ada pada successhandler Anda . Jadi, jika metode Anda hanya akan mengembalikan janji yang telah diselesaikan, seperti yang sering terjadi, Anda memerlukan trailing catchhandler (atau yang lainnya thendengan successparameter kosong ). Tentu, mungkin thenhandler Anda tidak melakukan apa pun yang berpotensi gagal, dalam hal ini menggunakan satu 2 parameterthen bisa baik-baik saja.

Tapi saya percaya inti dari teks yang Anda tautkan adalah yang thensebagian besar bermanfaat dibandingkan dengan panggilan balik dalam kemampuannya untuk mengaitkan beberapa langkah asinkron, dan ketika Anda benar-benar melakukan ini, bentuk 2-parameter dari thensubtly tidak berperilaku cukup seperti yang diharapkan , untuk alasan di atas. Ini terutama berlawanan dengan intuisi ketika digunakan rantai tengah.

Sebagai seseorang yang telah melakukan banyak hal async yang kompleks dan menabrak sudut-sudut seperti ini lebih dari yang saya akui, saya sangat merekomendasikan menghindari anti-pola ini dan menggunakan pendekatan penangan yang terpisah.

acjay
sumber
18

Dengan melihat kelebihan dan kekurangan dari keduanya, kita dapat membuat perkiraan yang tepat untuk situasi yang tepat. Ini adalah dua pendekatan utama untuk mengimplementasikan janji-janji. Keduanya memiliki kelebihan dan kekurangan

Tangkap Pendekatan

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Keuntungan

  1. Semua kesalahan ditangani oleh satu blok tangkapan.
  2. Bahkan menangkap pengecualian di blok itu.
  3. Rantai beberapa panggilan balik sukses

Kekurangan

  1. Dalam kasus chaining menjadi sulit untuk menampilkan pesan kesalahan yang berbeda.

Pendekatan Keberhasilan / Kesalahan

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

Keuntungan

  1. Anda mendapatkan kontrol kesalahan berbutir halus.
  2. Anda dapat memiliki fungsi penanganan kesalahan umum untuk berbagai kategori kesalahan seperti kesalahan db, 500 kesalahan dll.

Kerugian

  1. Anda masih membutuhkan yang lain catchjika Anda ingin menangani kesalahan yang dilemparkan oleh keberhasilan panggilan balik
aWebDeveloper
sumber
Untuk seseorang yang perlu men-debug masalah produksi hanya dengan menggunakan file log, saya lebih suka Pendekatan Keberhasilan / Kesalahan karena memberikan kemampuan untuk membuat rantai kesalahan kausal yang dapat dicatat pada batas keluar aplikasi Anda.
Shane Rowatt
pertanyaan. katakanlah saya melakukan panggilan async yang melakukan salah satu dari beberapa hal: 1) mengembalikan berhasil (kode status 2xx), 2) mengembalikan tidak berhasil (kode 4xx atau 5xx) tetapi tidak ditolak per se, 3) atau tidak kembali sama sekali ( koneksi internet sedang down). Untuk kasus # 1, panggilan balik sukses di. Kemudian dipukul. Untuk kasus # 2, panggilan balik kesalahan dalam .Kemudian dipukul. Untuk kasus # 3, .catch disebut. Ini analisis yang benar, bukan? Kasus # 2 paling sulit bc secara teknis 4xx atau 5xx bukan penolakan, masih berhasil kembali. Jadi, kita perlu menanganinya di dalam. Maka .... Apakah pemahaman saya benar?
Benjamin Hoffman
"Untuk kasus # 2, panggilan balik kesalahan di. Kemudian dipukul. Untuk kasus # 3, panggilan. Disebut analisis yang benar, kan?" - Begitulah cara kerjanya
aWebDeveloper
2

Penjelasan sederhana:

Dalam ES2018

Ketika metode catch disebut dengan argumen onRejected, langkah-langkah berikut diambil:

  1. Biarkan janji menjadi nilai ini.
  2. Kembali? Meminta (janji, "lalu", «tidak terdefinisi, pada Ditolak»).

itu berarti:

promise.then(f1).catch(f2)

sama dengan

promise.then(f1).then(undefiend, f2)
bitfishxyz
sumber
1

Menggunakan .then().catch()memungkinkan Anda mengaktifkan Promise Chaining yang diperlukan untuk memenuhi alur kerja. Anda mungkin perlu membaca beberapa informasi dari database kemudian Anda ingin meneruskannya ke API async kemudian Anda ingin memanipulasi respons. Anda mungkin ingin mendorong respons kembali ke database. Menangani semua alur kerja ini dengan konsep Anda bisa dilakukan tetapi sangat sulit untuk dikelola. Solusi yang lebih baik adalah then().then().then().then().catch()menerima semua kesalahan hanya dalam sekali penangkapan dan memungkinkan Anda menjaga kelestarian kode.

Jayant Varshney
sumber
0

Menggunakan then()dan catch()membantu rantai kesuksesan dan kegagalan penangan pada janji. catch()bekerja dengan janji dikembalikan oleh then(). Itu menangani,

  1. Jika janji ditolak. Lihat # 3 pada gambar
  2. Jika kesalahan terjadi pada penangan sukses maka (), antara nomor baris 4 hingga 7 di bawah ini. Lihat # 2.a dalam gambar (Kegagalan panggilan balik then()tidak menangani hal ini.)
  3. Jika kesalahan terjadi pada penangan kegagalan maka (), nomor baris 8 di bawah ini. Lihat # 3.b dalam gambar.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

masukkan deskripsi gambar di sini

Catatan : Sering kali, penangan kegagalan mungkin tidak didefinisikan jika catch()sudah ditulis. EDIT: reject()Hasil di memohon catch()hanya jika penangan kesalahan dalam then()adalah tidak didefinisikan. Perhatikan # 3 pada gambar di sebelah catch(). Itu dipanggil ketika pawang di baris # 8 dan 9 tidak didefinisikan.

Masuk akal karena janji yang dikembalikan oleh then()tidak memiliki kesalahan jika panggilan balik menanganinya.

VenCKi
sumber
Tanda panah dari nomor 3 ke catchpanggilan balik tampak salah.
Bergi
Terima kasih! Dengan callback kesalahan yang ditentukan di maka (), itu tidak dipanggil (baris # 8 dan # 9 dalam potongan kode). # 3 memanggil salah satu dari dua panah. Masuk akal karena janji yang dikembalikan saat itu () tidak memiliki kesalahan jika ada panggilan balik yang menanganinya. Diedit jawabannya!
VenCKi
-1

Alih-alih kata-kata, contoh yang bagus. Kode berikut (jika janji pertama terselesaikan):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

identik dengan:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Tetapi dengan janji pertama yang ditolak, ini tidak identik:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)
ktretyak
sumber
4
Ini tidak masuk akal, bisa tolong hapus jawaban ini? Ini menyesatkan dan mengalihkan perhatian dari jawaban yang benar.
Andy Ray
@AndyRay, ini tidak masuk akal dalam aplikasi nyata, tetapi masuk akal untuk memahami pekerjaan dari janji-janji.
ktretyak