Apakah saya perlu kembali setelah menyelesaikan / menolak lebih awal?

262

Misalkan saya memiliki kode berikut.

function divide(numerator, denominator) {
 return new Promise((resolve, reject) => {

  if(denominator === 0){
   reject("Cannot divide by 0");
   return; //superfluous?
  }

  resolve(numerator / denominator);

 });
}

Jika tujuan saya adalah menggunakan rejectuntuk keluar lebih awal, haruskah saya membiasakan returndiri juga setelahnya?

sam
sumber
5
Ya, karena proses penyelesaiannya

Jawaban:

371

The returntujuannya adalah untuk mengakhiri pelaksanaan fungsi setelah penolakan, dan mencegah eksekusi kode setelah.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {

    if (denominator === 0) {
      reject("Cannot divide by 0");
      return; // The function execution ends here 
    }

    resolve(numerator / denominator);
  });
}

Dalam hal ini mencegah resolve(numerator / denominator); dari mengeksekusi, yang tidak sepenuhnya diperlukan. Namun, masih lebih baik untuk menghentikan eksekusi untuk mencegah kemungkinan jebakan di masa depan. Selain itu, ini adalah praktik yang baik untuk mencegah menjalankan kode yang tidak perlu.

Latar Belakang

Sebuah janji bisa ada di salah satu dari 3 negara:

  1. tertunda - keadaan awal. Dari pending kita dapat pindah ke salah satu negara lain
  2. terpenuhi - operasi yang sukses
  3. ditolak - operasi gagal

Ketika sebuah janji dipenuhi atau ditolak, itu akan tetap di negara ini tanpa batas waktu (diselesaikan). Jadi, menolak janji yang dipenuhi atau memenuhi janji yang ditolak, tidak akan berpengaruh.

Cuplikan contoh ini menunjukkan bahwa meskipun janji itu dipenuhi setelah ditolak, tetap saja ditolak.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }

    resolve(numerator / denominator);
  });
}

divide(5,0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Jadi mengapa kita harus kembali?

Meskipun kami tidak dapat mengubah status janji yang telah ditentukan, menolak atau menyelesaikan tidak akan menghentikan eksekusi seluruh fungsi. Fungsi ini mungkin berisi kode yang akan membuat hasil membingungkan. Sebagai contoh:

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) {
      reject("Cannot divide by 0");
    }
    
    console.log('operation succeeded');

    resolve(numerator / denominator);
  });
}

divide(5, 0)
  .then((result) => console.log('result: ', result))
  .catch((error) => console.log('error: ', error));

Bahkan jika fungsi tidak mengandung kode seperti itu sekarang, ini menciptakan kemungkinan jebakan di masa depan. Refactor di masa depan mungkin mengabaikan fakta bahwa kode tersebut masih dieksekusi setelah janji ditolak, dan akan sulit untuk di-debug.

Menghentikan eksekusi setelah menyelesaikan / menolak:

Ini adalah hal-hal aliran kontrol JS standar.

  • Kembali setelah resolve/ reject:

  • Kembali dengan resolve/ reject- karena nilai balik dari callback diabaikan, kita dapat menyimpan garis dengan mengembalikan pernyataan tolak / tekad:

  • Menggunakan blok if / else:

Saya lebih suka menggunakan salah satu returnopsi karena kodenya lebih rata.

Ori Drori
sumber
28
Patut dicatat bahwa kode tidak akan benar-benar berperilaku berbeda jika returnada atau tidak karena setelah negara janji telah ditetapkan, itu tidak dapat diubah sehingga panggilan resolve()setelah panggilan reject()tidak akan melakukan apa pun kecuali menggunakan beberapa siklus CPU tambahan. Saya sendiri akan menggunakan returnkeadilan dari sudut pandang kode dan efisiensi, tetapi tidak diperlukan dalam contoh khusus ini.
jfriend00
1
Coba gunakan Promise.try(() => { })bukan Janji baru dan hindari menggunakan tekad / tolak panggilan. Alih-alih, Anda bisa menulis yang return denominator === 0 ? throw 'Cannot divide by zero' : numerator / denominator; saya gunakan Promise.trysebagai sarana untuk memulai suatu Janji serta menghilangkan janji-janji yang dibungkus dengan blok try / catch yang bermasalah.
kingdango
2
Itu baik untuk diketahui, dan saya suka polanya. Namun, saat ini Promise.try adalah saran tahap 0, jadi Anda hanya dapat menggunakannya dengan shim atau dengan menggunakan perpustakaan janji seperti bluebird atau Q.
Ori Drori
6
@ jfriend00 Jelas dalam contoh sederhana ini kode tidak akan berperilaku berbeda. Tetapi bagaimana jika Anda memiliki kode setelah rejectitu melakukan sesuatu yang mahal, seperti terhubung ke database atau titik akhir API? Semua itu tidak perlu dan memerlukan biaya dan sumber daya, terutama misalnya jika Anda terhubung ke sesuatu seperti database AWS atau titik akhir API Gateway. Dalam hal ini Anda pasti akan menggunakan pengembalian untuk menghindari kode yang tidak perlu dieksekusi.
Jake Wilson
3
@JakeWilson - Tentu saja, itu hanya aliran kode normal dalam Javascript dan tidak ada hubungannya dengan janji sama sekali. Jika Anda sudah selesai memproses fungsi dan tidak ingin ada kode lebih untuk mengeksekusi dalam kode jalan saat ini, Anda menyisipkan return.
jfriend00
37

Sebuah idiom umum, yang mungkin atau mungkin bukan cangkir teh Anda, adalah untuk menggabungkan returndengan reject, untuk secara bersamaan menolak janji dan keluar dari fungsi, sehingga sisa fungsi termasuk yang resolvetidak dijalankan. Jika Anda menyukai gaya ini, itu bisa membuat kode Anda sedikit lebih kompak.

function divide(numerator, denominator) {
  return new Promise((resolve, reject) => {
    if (denominator === 0) return reject("Cannot divide by 0");
                           ^^^^^^^^^^^^^^
    resolve(numerator / denominator);
  });
}

Ini berfungsi dengan baik karena konstruktor Janji tidak melakukan apa pun dengan nilai pengembalian, dan dalam hal apa pun resolveserta rejecttidak mengembalikan apa pun.

Idiom yang sama dapat digunakan dengan gaya panggilan balik yang ditunjukkan dalam jawaban lain:

function divide(nom, denom, cb){
  if(denom === 0) return cb(Error("Cannot divide by zero"));
                  ^^^^^^^^^
  cb(null, nom / denom);
} 

Sekali lagi, ini berfungsi dengan baik karena orang yang menelepon dividetidak mengharapkannya mengembalikan apa pun dan tidak melakukan apa pun dengan nilai pengembalian.


sumber
6
Saya tidak suka ini. Ini memberi kesan bahwa Anda mengembalikan sesuatu yang sebenarnya tidak Anda kembalikan. Anda memohon fungsi tolak dan kemudian gunakan kembali untuk mengakhiri eksekusi fungsi. Pertahankan mereka pada jalur yang berbeda, apa yang Anda lakukan hanya akan membingungkan orang. Pembacaan kode adalah raja.
K - Keracunan dalam SO tumbuh.
7
@ KarlMorrison Anda sebenarnya mengembalikan "sesuatu", janji yang ditolak. Saya pikir "gagasan" yang Anda bicarakan itu sangat pribadi. Tidak ada yang salah dengan mengembalikan rejectstatus
Frondor
5
@Frondor Saya tidak berpikir Anda mengerti apa yang saya tulis. Tentu saja Anda dan saya mengerti ini, tidak ada yang terjadi ketika mengembalikan penolakan pada baris yang sama. Tapi bagaimana dengan pengembang yang tidak begitu terbiasa dengan JavaScript yang masuk ke proyek? Jenis pemrograman ini mengurangi keterbacaan untuk orang-orang seperti itu. Sistem ekologi JavaScript saat ini cukup berantakan dan orang-orang yang menyebarkan praktik semacam ini hanya akan memperburuknya. Ini praktik buruk.
K - Keracunan dalam SO tumbuh.
1
@KarlMorrison Pendapat pribadi! = Praktik buruk. Mungkin akan membantu pengembang Javascript baru untuk memahami apa yang terjadi dengan pengembalian.
Toby Caulk
1
@TobyCaulk Jika orang perlu belajar apa yang dikembalikan maka mereka tidak boleh bermain-main dengan Janji, mereka harus belajar pemrograman dasar.
K - Keracunan dalam SO tumbuh.
10

Secara teknis tidak diperlukan di sini 1 - karena Janji dapat diselesaikan atau ditolak, secara eksklusif dan hanya sekali. Hasil Janji pertama menang dan setiap hasil selanjutnya diabaikan. Ini berbeda dari panggilan balik gaya Node.

Yang sedang dikatakan itu adalah praktik bersih yang baik untuk memastikan bahwa tepat seseorang dipanggil, ketika praktis, dan memang dalam kasus ini karena tidak ada proses async / ditangguhkan lebih lanjut. Keputusan untuk "kembali lebih awal" tidak berbeda dengan mengakhiri fungsi apa pun ketika pekerjaannya selesai - vs. melanjutkan pemrosesan yang tidak terkait atau tidak perlu.

Kembali pada waktu yang tepat (atau menggunakan kondisional untuk menghindari mengeksekusi kasus "lain") mengurangi kemungkinan kode berjalan secara tidak sengaja dalam keadaan tidak valid atau melakukan efek samping yang tidak diinginkan; dan karena itu membuat kode kurang rentan terhadap 'melanggar tak terduga'.


1 Jawaban teknis ini juga bergantung pada kenyataan bahwa dalam hal ini kode setelah "kembali", jika dihilangkan, tidak akan menghasilkan efek samping. JavaScript akan dengan senang hati membagi dengan nol dan mengembalikan + Infinity / -Infinity atau NaN.

pengguna2864740
sumber
Catatan kaki yang bagus !!
HankCa
9

Jika Anda tidak "kembali" setelah menyelesaikan / menolak, hal-hal buruk (seperti pengalihan halaman) dapat terjadi setelah Anda bermaksud menghentikannya. Sumber: Saya mengalami ini.

Benjamin H
sumber
6
+1 sebagai contoh. Saya memiliki masalah di mana program saya akan membuat 100+ query database tidak valid dan saya tidak tahu mengapa. Ternyata saya tidak "kembali" setelah ditolak. Ini kesalahan kecil tapi saya belajar pelajaran saya.
AdamInTheOculus
8

Jawaban oleh Ori sudah menjelaskan bahwa itu tidak perlu return tetapi merupakan praktik yang baik. Perhatikan bahwa konstruktor janji aman, sehingga akan mengabaikan pengecualian yang dilemparkan yang dilewati kemudian, pada dasarnya Anda memiliki efek samping yang tidak dapat Anda amati dengan mudah.

Perhatikan bahwa returning awal juga sangat umum dalam panggilan balik:

function divide(nom, denom, cb){
     if(denom === 0){
         cb(Error("Cannot divide by zero");
         return; // unlike with promises, missing the return here is a mistake
     }
     cb(null, nom / denom); // this will divide by zero. Since it's a callback.
} 

Jadi, sementara itu praktik yang baik dalam janji-janji itu diperlukan dengan panggilan balik. Beberapa catatan tentang kode Anda:

  • Kasus penggunaan Anda bersifat hipotesis, jangan benar-benar menggunakan janji dengan tindakan sinkron.
  • Konstruktor janji mengabaikan nilai kembali. Beberapa perpustakaan akan memperingatkan jika Anda mengembalikan nilai yang tidak ditentukan untuk memperingatkan Anda terhadap kesalahan kembali ke sana. Sebagian besar tidak sepintar itu.
  • Konstruktor janji dilemparkan dengan aman, itu akan mengkonversi pengecualian menjadi penolakan tetapi seperti yang orang lain tunjukkan - janji diputuskan sekali.
Benjamin Gruenbaum
sumber
4

Dalam banyak kasus dimungkinkan untuk memvalidasi parameter secara terpisah dan segera mengembalikan janji yang ditolak dengan Promise.reject (alasan) .

function divide2(numerator, denominator) {
  if (denominator === 0) {
    return Promise.reject("Cannot divide by 0");
  }
  
  return new Promise((resolve, reject) => {
    resolve(numerator / denominator);
  });
}


divide2(4, 0).then((result) => console.log(result), (error) => console.log(error));

Dorad
sumber