Memperbarui:
Untuk membantu pemirsa postingan ini di masa mendatang, saya membuat demo jawaban pluma ini .
Pertanyaan:
Tujuan saya tampaknya cukup mudah.
step(1)
.then(function() {
return step(2);
}, function() {
stepError(1);
return $q.reject();
})
.then(function() {
}, function() {
stepError(2);
});
function step(n) {
var deferred = $q.defer();
//fail on step 1
(n === 1) ? deferred.reject() : deferred.resolve();
return deferred.promise;
}
function stepError(n) {
console.log(n);
}
Masalahnya di sini adalah jika saya gagal pada langkah 1, stepError(1)
DAN keduanya stepError(2)
dipecat. Jika saya tidak return $q.reject
maka stepError(2)
tidak akan dipecat, tetapi step(2)
akan, yang saya mengerti. Saya telah mencapai segalanya kecuali apa yang saya coba lakukan.
Bagaimana cara menulis promise sehingga saya bisa memanggil fungsi saat ditolak, tanpa memanggil semua fungsi di rantai error? Atau apakah ada cara lain untuk melakukannya?
Ini demo langsung jadi Anda punya sesuatu untuk dikerjakan.
Memperbarui:
Saya sepertinya telah menyelesaikannya. Di sini, saya menangkap kesalahan di akhir rantai dan meneruskan data reject(data)
sehingga saya akan tahu masalah apa yang harus ditangani dalam fungsi kesalahan. Ini sebenarnya tidak memenuhi persyaratan saya karena saya tidak ingin bergantung pada data. Ini akan menjadi timpang, tetapi dalam kasus saya akan lebih bersih untuk melewatkan callback kesalahan ke fungsi daripada bergantung pada data yang dikembalikan untuk menentukan apa yang harus dilakukan.
step(1)
.then(function() {
return step(2);
})
.then(function() {
return step(3);
})
.then(false,
function(x) {
stepError(x);
}
);
function step(n) {
console.log('Step '+n);
var deferred = $q.defer();
(n === 1) ? deferred.reject(n) : deferred.resolve(n);
return deferred.promise;
}
function stepError(n) {
console.log('Error '+n);
}
Promise.prototype.catch()
contoh di MDN menunjukkan solusi untuk masalah yang sama persis.Jawaban:
Alasan kode Anda tidak berfungsi seperti yang diharapkan adalah karena kode tersebut melakukan sesuatu yang berbeda dari yang Anda kira.
Katakanlah Anda memiliki sesuatu seperti berikut:
stepOne() .then(stepTwo, handleErrorOne) .then(stepThree, handleErrorTwo) .then(null, handleErrorThree);
Untuk lebih memahami apa yang terjadi, anggaplah ini adalah kode sinkron dengan
try
/catch
blok:try { try { try { var a = stepOne(); } catch(e1) { a = handleErrorOne(e1); } var b = stepTwo(a); } catch(e2) { b = handleErrorTwo(e2); } var c = stepThree(b); } catch(e3) { c = handleErrorThree(e3); }
The
onRejected
handler (argumen keduathen
) pada dasarnya adalah mekanisme koreksi kesalahan (seperticatch
blok). Jika kesalahan dilemparkanhandleErrorOne
, itu akan ditangkap oleh blok catch berikutnya (catch(e2)
), dan seterusnya.Ini jelas bukan yang Anda inginkan.
Katakanlah kita ingin seluruh rantai resolusi gagal tidak peduli apa yang salah:
stepOne() .then(function(a) { return stepTwo(a).then(null, handleErrorTwo); }, handleErrorOne) .then(function(b) { return stepThree(b).then(null, handleErrorThree); });
Catatan: Kita bisa membiarkannya
handleErrorOne
, karena hanya akan dipanggil jikastepOne
ditolak (ini adalah fungsi pertama dalam rantai, jadi kita tahu bahwa jika rantai ditolak pada titik ini, itu hanya bisa karena janji fungsi itu) .Perubahan penting adalah bahwa penangan kesalahan untuk fungsi lain bukan bagian dari rantai perjanjian utama. Sebaliknya, setiap langkah memiliki "sub-rantai" -nya sendiri dengan
onRejected
yang hanya dipanggil jika langkah tersebut ditolak (tetapi tidak dapat dicapai oleh rantai utama secara langsung).Alasan ini berhasil adalah karena keduanya
onFulfilled
danonRejected
merupakan argumen opsional untukthen
metode tersebut. Jika sebuah promise terpenuhi (yaitu diselesaikan) danthen
rantai berikutnya tidak memilikionFulfilled
penangan, rantai akan berlanjut sampai ada penangan dengan penangan tersebut.Ini berarti dua baris berikut ini setara:
stepOne().then(stepTwo, handleErrorOne) stepOne().then(null, handleErrorOne).then(stepTwo)
Tetapi baris berikut tidak sama dengan dua di atas:
stepOne().then(stepTwo).then(null, handleErrorOne)
Pustaka janji Angular
$q
didasarkan pada pustaka kriskowalQ
(yang memiliki API yang lebih kaya, tetapi berisi semua yang dapat Anda temukan di dalamnya$q
). Dokumen API Q di GitHub terbukti berguna. Q mengimplementasikan spesifikasi Promises / A + , yang menjelaskan secara mendetail tentang carathen
dan perilaku penyelesaian promise berfungsi dengan tepat.EDIT:
Juga perlu diingat bahwa jika Anda ingin keluar dari rantai di penangan kesalahan Anda, itu perlu mengembalikan janji yang ditolak atau melempar Kesalahan (yang akan tertangkap dan dibungkus dalam janji yang ditolak secara otomatis). Jika Anda tidak mengembalikan suatu janji,
then
bungkus nilai pengembaliannya dalam sebuah janji keputusan untuk Anda.Ini berarti bahwa jika Anda tidak mengembalikan apa pun, Anda secara efektif mengembalikan janji yang telah ditetapkan untuk nilainya
undefined
.sumber
if you don't return anything, you are effectively returning a resolved promise for the value undefined.
Terima kasih @plumastepOne().then(stepTwo, handleErrorOne)
`stepOne (). then (null, handleErrorOne) .then (stepTwo)` Apakah ini benar-benar setara? Saya pikir jika terjadi penolakan distepOne
baris kedua kode akan dieksekusistepTwo
tetapi yang pertama hanya akan mengeksekusihandleErrorOne
dan berhenti. Atau apakah saya melewatkan sesuatu?Agak terlambat ke pesta tetapi solusi sederhana ini berhasil untuk saya:
function chainError(err) { return Promise.reject(err) }; stepOne() .then(stepTwo, chainError) .then(stepThreee, chainError);
Hal ini memungkinkan Anda untuk memecahkan keluar dari rantai.
sumber
.then(user => { if (user) return Promise.reject('The email address already exists.') })
.then(user => { if (user) throw 'The email address already exists.' })
Yang Anda butuhkan adalah
.then()
rantai berulang dengan kasing khusus untuk memulai dan kasing khusus untuk diselesaikan.Keahliannya adalah untuk mendapatkan nomor langkah dari kasus kegagalan untuk melewati ke penanganan kesalahan akhir.
step(1)
tanpa syarat..then()
dengan callback berikut:.then()
tanpa penangan sukses dan penangan kesalahan akhir.Anda dapat menulis semuanya dengan tangan tetapi lebih mudah untuk mendemonstrasikan pola dengan fungsi yang diberi nama dan digeneralisasikan:
function nextStep(n) { return step(n + 1); } function step(n) { console.log('step ' + n); var deferred = $q.defer(); (n === 3) ? deferred.reject(n) : deferred.resolve(n); return deferred.promise; } function stepError(n) { throw(n); } function finalError(n) { console.log('finalError ' + n); } step(1) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(nextStep, stepError) .then(null, finalError);});
lihat demo
Perhatikan bagaimana masuk
step()
, penangguhan ditolak atau diselesaikan dengann
, sehingga membuat nilai tersebut tersedia untuk callback berikutnya.then()
dalam rantai. SetelahstepError
dipanggil, kesalahan berulang kali ditampilkan kembali hingga ditangani olehfinalError
.sumber
Saat menolak, Anda harus meneruskan kesalahan penolakan, kemudian membungkus penanganan kesalahan langkah dalam fungsi yang memeriksa apakah penolakan harus diproses atau "dicabut kembali" hingga akhir rantai:
// function mocking steps function step(i) { i++; console.log('step', i); return q.resolve(i); } // function mocking a failing step function failingStep(i) { i++; console.log('step '+ i + ' (will fail)'); var e = new Error('Failed on step ' + i); e.step = i; return q.reject(e); } // error handler function handleError(e){ if (error.breakChain) { // handleError has already been called on this error // (see code bellow) log('errorHandler: skip handling'); return q.reject(error); } // firs time this error is past to the handler console.error('errorHandler: caught error ' + error.message); // process the error // ... // error.breakChain = true; return q.reject(error); } // run the steps, will fail on step 4 // and not run step 5 and 6 // note that handleError of step 5 will be called // but since we use that error.breakChain boolean // no processing will happen and the error will // continue through the rejection path until done(,) step(0) // 1 .catch(handleError) .then(step) // 2 .catch(handleError) .then(step) // 3 .catch(handleError) .then(failingStep) // 4 fail .catch(handleError) .then(step) // 5 .catch(handleError) .then(step) // 6 .catch(handleError) .done(function(){ log('success arguments', arguments); }, function (error) { log('Done, chain broke at step ' + error.step); });
Apa yang Anda lihat di konsol:
step 1 step 2 step 3 step 4 (will fail) errorHandler: caught error 'Failed on step 4' errorHandler: skip handling errorHandler: skip handling Done, chain broke at step 4
Berikut beberapa kode yang berfungsi https://jsfiddle.net/8hzg5s7m/3/
Jika Anda memiliki penanganan khusus untuk setiap langkah, pembungkus Anda bisa seperti ini:
/* * simple wrapper to check if rejection * has already been handled * @param function real error handler */ function createHandler(realHandler) { return function(error) { if (error.breakChain) { return q.reject(error); } realHandler(error); error.breakChain = true; return q.reject(error); } }
lalu rantaimu
step1() .catch(createHandler(handleError1Fn)) .then(step2) .catch(createHandler(handleError2Fn)) .then(step3) .catch(createHandler(handleError3Fn)) .done(function(){ log('success'); }, function (error) { log('Done, chain broke at step ' + error.step); });
sumber
Jika saya mengerti dengan benar, Anda hanya ingin menampilkan kesalahan untuk langkah yang gagal, bukan?
Itu harus sesederhana mengubah kasus kegagalan janji pertama menjadi ini:
step(1).then(function (response) { step(2); }, function (response) { stepError(1); return response; }).then( ... )
Dengan kembali
$q.reject()
pada kasus kegagalan langkah pertama, Anda menolak janji itu, yang menyebabkan errorCallback dipanggil di langkah keduathen(...)
.sumber
step(2)
. Sekarang saya coba lagi itu tidak terjadi. Aku begitu bingung.return step(2);
itu hanya boleh dipanggil jikastep(1)
penyelesaian berhasil.return $q.reject()
, rantai akan terus berjalan. Dalam hal inireturn response
mengacaukannya. Lihat ini: jsbin.com/EpaZIsIp/6/editvar s = 1; start() .then(function(){ return step(s++); }) .then(function() { return step(s++); }) .then(function() { return step(s++); }) .then(0, function(e){ console.log(s-1); });
http://jsbin.com/EpaZIsIp/20/edit
Atau otomatis untuk sejumlah langkah:
var promise = start(); var s = 1; var l = 3; while(l--) { promise = promise.then(function() { return step(s++); }); } promise.then(0, function(e){ console.log(s-1); });
http://jsbin.com/EpaZIsIp/21/edit
sumber
deferred.reject(n)
maka saya mendapatkan peringatan bahwa janji ditolak dengan objekCoba ro gunakan ini seperti libs:
https://www.npmjs.com/package/promise-chain-break
db.getData() .then(pb((data) => { if (!data.someCheck()) { tellSomeone(); // All other '.then' calls will be skiped return pb.BREAK; } })) .then(pb(() => { })) .then(pb(() => { })) .catch((error) => { console.error(error); });
sumber
Jika Anda ingin menyelesaikan masalah ini menggunakan async / await:
(async function(){ try { const response1, response2, response3 response1 = await promise1() if(response1){ response2 = await promise2() } if(response2){ response3 = await promise3() } return [response1, response2, response3] } catch (error) { return [] } })()
sumber
Lampirkan penangan kesalahan sebagai elemen rantai terpisah langsung ke pelaksanaan langkah-langkah:
// Handle errors for step(1) step(1).then(null, function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).then(null, function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).then(null, function() { stepError(3); return $q.reject(); }); });
atau menggunakan
catch()
:// Handle errors for step(1) step(1).catch(function() { stepError(1); return $q.reject(); }) .then(function() { // Attach error handler for step(2), // but only if step(2) is actually executed return step(2).catch(function() { stepError(2); return $q.reject(); }); }) .then(function() { // Attach error handler for step(3), // but only if step(3) is actually executed return step(3).catch(function() { stepError(3); return $q.reject(); }); });
Catatan: Ini pada dasarnya adalah pola yang sama seperti yang disarankan oleh pluma dalam jawabannya tetapi menggunakan penamaan OP.
sumber
Promise.prototype.catch()
Contoh yang ditemukan di MDN di bawah ini sangat membantu.(Jawaban yang diterima menyebutkan
then(null, onErrorHandler)
yang pada dasarnya sama dengancatch(onErrorHandler)
.)sumber
Solusi terbaik adalah melakukan refactor pada rantai janji Anda untuk menggunakan ES6 menunggu. Kemudian Anda bisa kembali dari fungsi untuk melewati perilaku lainnya.
Saya telah membenturkan kepala saya terhadap pola ini selama lebih dari setahun dan menggunakan await's adalah surga.
sumber
Jika suatu saat Anda kembali,
Promise.reject('something')
Anda akan dilemparkan ke dalam blok tangkapan pada janji itu.promiseOne .then((result) => { if (!result) { return Promise.reject('No result'); } return; }) .catch((err) => { console.log(err); });
Jika janji pertama tidak memberikan hasil apa pun, Anda hanya akan mendapatkan 'Tidak ada hasil' di konsol.
sumber
Gunakan Modul SequentialPromise
Niat
Sediakan modul yang tanggung jawabnya untuk menjalankan permintaan secara berurutan, sambil melacak indeks saat ini dari setiap operasi secara ordinal. Tentukan operasi dalam Pola Perintah untuk fleksibilitas.
Peserta
execute
metode untuk merangkai & melacak setiap operasi. SequentialPromise mengembalikan Rantai-Janji dari semua operasi yang dilakukan.execute
metodenya sambil meneruskan daftar opsi ordinal untuk setiap operasi.Konsekuensi
Gunakan SequentialPromise ketika perilaku ordinal dari resolusi Promise diperlukan. SequentialPromise akan melacak indeks yang Janji ditolaknya.
Penerapan
clear(); var http = { get(url) { var delay = Math.floor( Math.random() * 10 ), even = !(delay % 2); var xhr = new Promise(exe); console.log(`REQUEST`, url, delay); xhr.then( (data) => console.log(`SUCCESS: `, data) ).catch( (data) => console.log(`FAILURE: `, data) ); function exe(resolve, reject) { var action = { 'true': reject, 'false': resolve }[ even ]; setTimeout( () => action({ url, delay }), (1000 * delay) ); } return xhr; } }; var SequentialPromise = new (function SequentialPromise() { var PRIVATE = this; return class SequentialPromise { constructor(context, action) { this.index = 0; this.requests = [ ]; this.context = context; this.action = action; return this; } log() {} execute(url, ...more) { var { context, action, requests } = this; var chain = context[action](url); requests.push(chain); chain.then( (data) => this.index += 1 ); if (more.length) return chain.then( () => this.execute(...more) ); return chain; } }; })(); var sequence = new SequentialPromise(http, 'get'); var urls = [ 'url/name/space/0', 'url/name/space/1', 'url/name/space/2', 'url/name/space/3', 'url/name/space/4', 'url/name/space/5', 'url/name/space/6', 'url/name/space/7', 'url/name/space/8', 'url/name/space/9' ]; var chain = sequence.execute(...urls); var promises = sequence.requests; chain.catch( () => console.warn(`EXECUTION STOPPED at ${sequence.index} for ${urls[sequence.index]}`) ); // console.log('>', chain, promises);
Inti
SequentialPromise
sumber