Janji JavaScript - tolak vs. lempar

385

Saya telah membaca beberapa artikel tentang hal ini, tetapi masih belum jelas bagi saya jika ada perbedaan antara Promise.rejectvs melempar kesalahan. Sebagai contoh,

Menggunakan Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Menggunakan lemparan

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

Preferensi saya adalah menggunakan throwhanya karena lebih pendek, tetapi bertanya-tanya apakah ada kelebihan satu di atas yang lain.

Naresh
sumber
9
Kedua metode menghasilkan respons yang sama persis. The .then()handler menangkap pengecualian dilemparkan dan mengubahnya menjadi sebuah janji ditolak secara otomatis. Karena saya telah membaca bahwa pengecualian yang dilemparkan tidak terlalu cepat untuk dieksekusi, saya akan menduga bahwa mengembalikan janji yang ditolak mungkin sedikit lebih cepat untuk dieksekusi, tetapi Anda harus merancang tes di beberapa browser modern jika itu penting untuk diketahui. Saya pribadi menggunakan throwkarena saya suka keterbacaan.
jfriend00
@webduvet not with Promises - mereka dirancang untuk bekerja dengan throw.
joews
15
Satu kelemahan throwadalah bahwa itu tidak akan menghasilkan janji yang ditolak jika dilempar dari dalam panggilan balik yang tidak sinkron, seperti setTimeout. jsfiddle.net/m07van33 @Blondie jawaban Anda benar.
Kevin B
@ Joews bukan berarti itu baik;)
webduvet
1
Ah benar Jadi klarifikasi terhadap komentar saya adalah, "jika dilempar dari dalam panggilan balik yang tidak sinkron dan tidak dijanjikan " . Saya tahu ada pengecualian untuk itu, saya hanya tidak ingat apa itu. Saya juga lebih suka menggunakan lemparan hanya karena saya merasa lebih mudah dibaca, dan memungkinkan saya untuk menghilangkannya rejectdari daftar param saya.
Kevin B

Jawaban:

346

Tidak ada keuntungan menggunakan satu vs yang lain, tetapi, ada kasus khusus di mana throwtidak akan berfungsi. Namun, kasus-kasus itu dapat diperbaiki.

Setiap kali Anda berada dalam janji panggilan balik, Anda dapat menggunakan throw. Namun, jika Anda menggunakan panggilan balik asinkron lainnya, Anda harus menggunakannya reject.

Misalnya, ini tidak akan memicu tangkapan:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

Alih-alih, Anda pergi dengan janji yang tidak terselesaikan dan pengecualian yang tidak tertangkap. Itu adalah kasus di mana Anda ingin menggunakannya reject. Namun, Anda bisa memperbaikinya dengan dua cara.

  1. dengan menggunakan fungsi tolak Janji asli di dalam batas waktu:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. dengan menjanjikan batas waktu:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});

Kevin B
sumber
54
Layak disebutkan bahwa tempat-tempat di dalam panggilan balik async yang tidak dapat Anda promosikan tidak dapat digunakan throw error, Anda juga tidak dapat menggunakan return Promise.reject(err)apa yang diminta OP untuk dibandingkan. Ini pada dasarnya mengapa Anda tidak harus memasukkan callback async ke dalam janji. Promisikan semua yang async dan kemudian Anda tidak memiliki batasan ini.
jfriend00
9
"Namun, jika Anda berada dalam jenis panggilan balik lain" benar-benar harus "Namun, jika Anda berada dalam jenis panggilan balik asinkron apa pun ". Panggilan balik bisa sinkron (misalnya dengan Array#forEach) dan dengan itu, melempar ke dalamnya akan berhasil.
Félix Saparelli
2
@KevinB membaca kalimat ini "ada kasus khusus di mana lemparan tidak akan bekerja." dan "Setiap kali Anda berada dalam janji balik janji, Anda dapat menggunakan lemparan. Namun, jika Anda berada dalam panggilan balik tidak sinkron lainnya, Anda harus menggunakan tolak." Saya merasa bahwa contoh cuplikan akan menunjukkan kasus di mana throwtidak akan berfungsi dan sebagai gantinya Promise.rejectadalah pilihan yang lebih baik. Namun cuplikan tidak terpengaruh dengan salah satu dari dua pilihan tersebut dan memberikan hasil yang sama terlepas dari apa yang Anda pilih. Apakah saya melewatkan sesuatu?
Anshul
2
Iya. jika Anda menggunakan lemparan dalam setTimeout, hasil tangkapan tidak akan dipanggil. Anda harus menggunakan rejectyang diteruskan ke new Promise(fn)panggilan balik.
Kevin B
2
@KevinB terima kasih untuk tetap bersama. Contoh yang diberikan oleh OP menyebutkan ia secara spesifik ingin membandingkan return Promise.reject()dan throw. Dia tidak menyebutkan rejectpanggilan balik yang diberikan dalam new Promise(function(resolve, reject))konstruksi. Jadi sementara dua cuplikan Anda menunjukkan dengan tepat kapan Anda harus menggunakan panggilan balik tekad, pertanyaan OP bukan itu.
Anshul
202

Fakta penting lainnya adalah reject() JANGAN menghentikan aliran kontrol seperti returnpernyataan. Sebaliknyathrow menghentikan aliran kontrol.

Contoh:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

vs.

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

lukyer
sumber
51
Yah intinya benar tetapi perbandingannya rumit. Karena biasanya Anda harus mengembalikan janji yang ditolak dengan menulis return reject(), sehingga baris berikutnya tidak akan berjalan.
AZ.
7
Mengapa Anda ingin mengembalikannya?
Lukas
31
Dalam hal ini, return reject()hanya singkatan untuk reject(); returnapa yang Anda inginkan adalah untuk menghentikan aliran. Nilai kembali dari pelaksana (fungsi dilewatkan ke new Promise) tidak digunakan, jadi ini aman.
Félix Saparelli
47

Ya, perbedaan terbesar adalah bahwa menolak adalah fungsi panggilan balik yang dilakukan setelah janji ditolak, sedangkan melempar tidak dapat digunakan secara tidak sinkron. Jika Anda memilih untuk menggunakan tolak, kode Anda akan terus berjalan secara normal dalam mode asinkron saat melempar akan memprioritaskan menyelesaikan fungsi penyelesai (fungsi ini akan segera berjalan).

Contoh yang pernah saya lihat yang membantu mengklarifikasi masalah bagi saya adalah Anda dapat mengatur fungsi Timeout dengan tolak, misalnya:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

Di atas tidak mungkin menulis dengan lemparan.

Dalam contoh kecil Anda perbedaan dalam dibedakan tetapi ketika berhadapan dengan konsep asinkron yang lebih rumit perbedaan antara keduanya bisa drastis.

Blondie
sumber
1
Ini terdengar seperti konsep kunci, tetapi saya tidak memahaminya seperti yang tertulis. Masih terlalu baru untuk Janji, kurasa.
David Spector
43

TLDR: Suatu fungsi sulit untuk digunakan ketika kadang-kadang mengembalikan janji dan kadang-kadang melempar pengecualian. Saat menulis fungsi async, lebih suka memberi sinyal kegagalan dengan mengembalikan janji yang ditolak

Contoh khusus Anda mengaburkan beberapa perbedaan penting di antara mereka:

Karena Anda salah menangani di dalam rantai janji, pengecualian yang dilemparkan dapat dikonversi secara otomatis menjadi janji yang ditolak. Ini mungkin menjelaskan mengapa mereka tampaknya dapat dipertukarkan - tidak.

Pertimbangkan situasi di bawah ini:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

Ini akan menjadi anti-pola karena Anda perlu mendukung kasus kesalahan async dan sinkronisasi. Itu mungkin terlihat seperti:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

Tidak bagus dan di sinilah tempat Promise.reject(tersedia dalam lingkup global) untuk menyelamatkan dan secara efektif membedakan dirinya throw. Refactor sekarang menjadi:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

Ini sekarang memungkinkan Anda menggunakan hanya satu catch()untuk kegagalan jaringan dan pemeriksaan kesalahan sinkron untuk kekurangan token:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }
Maxwell
sumber
1
Contoh Op selalu memberikan janji. Pertanyaannya mengacu pada apakah Anda harus menggunakan Promise.rejectatau throwketika Anda ingin mengembalikan janji yang ditolak (janji yang akan melompat ke yang berikutnya .catch()).
Marcos Pereira
@ Maxwell - Saya suka Anda contoh. Pada saat yang sama jika pada pengambilan Anda akan menambahkan tangkapan dan di dalamnya Anda membuang pengecualian maka Anda akan aman untuk menggunakan coba ... menangkap ... Tidak ada dunia yang sempurna pada aliran pengecualian, tapi saya pikir menggunakan satu satu pola masuk akal, dan menggabungkan pola-pola itu tidak aman (selaras dengan pola Anda vs analogi anti-pola).
user3053247
1
Jawaban yang sangat bagus tetapi saya menemukan cacat di sini - pola ini mengasumsikan semua kesalahan ditangani dengan mengembalikan Promise.reject - apa yang terjadi dengan semua kesalahan tak terduga yang mungkin dibuang dari checkCredentials ()?
chenop
1
Ya, Anda benar @chenop - untuk menangkap kesalahan tak terduga tersebut, Anda harus tetap menggunakan try / catch
maxwell
Saya tidak mengerti kasus @ maxwell. Tidak bisakah Anda menyusunnya begitu saja checkCredentials(x).then(onFulfilled).catch(e) {}, dan memiliki catchpegangan baik kotak penolakan maupun kotak kesalahan yang dilemparkan?
Ben Wheeler
5

Contoh untuk dicoba. Cukup ubah isVersionThrow to false untuk menggunakan tolak alih-alih membuang.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

Chris Livdahl
sumber