この投稿の今後の視聴者を支援するために、プラマの答えのこのデモ 。を作成しました
私の目標はかなり簡単なようです。
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);
}
ここでの問題は、ステップ1で失敗すると、stepError(1)
とstepError(2)
の両方が起動されることです。 return $q.reject
を実行しないと、stepError(2)
は実行されませんが、step(2)
は実行されます。私がやろうとしていること以外のすべてを達成しました。
エラーチェーン内のすべての関数を呼び出さずに、拒否時に関数を呼び出すことができるように、どうすればプロミスを書くことができますか?または、これを達成する別の方法はありますか?
私種類のはそれを解決しました。ここでは、チェーンの最後でエラーをキャッチし、データをreject(data)
に渡しているため、エラー関数で処理する問題を把握できます。データに依存したくないので、これは実際に私の要件を満たしていません。足りないかもしれませんが、私の場合は、返されたデータに依存して何をすべきかを判断するのではなく、関数にエラーコールバックを渡す方がきれいです。
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);
}
コードが期待どおりに機能しないのは、実際にあなたが思っているのとは異なることをしているからです。
次のようなものがあるとしましょう:
stepOne()
.then(stepTwo, handleErrorOne)
.then(stepThree, handleErrorTwo)
.then(null, handleErrorThree);
何が起こっているのかをよりよく理解するために、これがtry
name __/catch
name__ブロックを持つ同期コードであると仮定しましょう:
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);
}
onRejected
name__ハンドラー(then
name__の2番目の引数)は、本質的に(catch
name__ブロックのような)エラー修正メカニズムです。 handleErrorOne
name__でエラーがスローされると、次のcatchブロック(catch(e2)
)などでキャッチされます。
これは明らかに意図したものではありません。
何がうまくいかなくても、解決チェーン全体が失敗するようにしたいとしましょう。
stepOne()
.then(function(a) {
return stepTwo(a).then(null, handleErrorTwo);
}, handleErrorOne)
.then(function(b) {
return stepThree(b).then(null, handleErrorThree);
});
注:handleErrorOne
name__は、stepOne
name__が拒否された場合にのみ呼び出されるため、onRejected
name__をそのままにしておくことができます(チェーンの最初の関数です。したがって、この時点でチェーンが拒否された場合、約束する)。
重要な変更は、他の関数のエラーハンドラーがメインのpromiseチェーンの一部ではないことです。代わりに、各ステップには、ステップが拒否された場合にのみ呼び出される__main_variable_name__を持つ独自の「サブチェーン」があります(ただし、メインチェーンから直接到達することはできません)。
これが機能する理由は、onFulfilled
name__とonRejected
name__の両方がthen
name__メソッドのオプションの引数であるためです。約束が満たされた場合(つまり、解決された場合)、チェーン内の次のthen
name__にonFulfilled
name__ハンドラーがない場合、チェーンはそのようなハンドラーを持つものが存在するまで継続します。
つまり、次の2行は同等です。
stepOne().then(stepTwo, handleErrorOne)
stepOne().then(null, handleErrorOne).then(stepTwo)
ただし、次の行は、上記の2つとnotに相当します。
stepOne().then(stepTwo).then(null, handleErrorOne)
Angularのpromiseライブラリ$q
は、kriskowalのQ
name__ライブラリ(より豊富なAPIを備えていますが、$q
にあるすべてのものが含まれています)に基づいています。 Q's API docs GitHubの有用性を証明できます。 Qは Promises/A + spec を実装します。これは、then
name__およびpromise解決動作が正確に機能する方法について詳しく説明します。
編集:
また、エラーハンドラでチェーンから抜け出すには、拒否されたプロミスを返すか、エラーをスローする必要があることに注意してください(エラーはキャッチされ、拒否されたプロミスに自動的にラップされます)。 promiseを返さない場合、then
name__は戻り値を解決プロミスでラップします。
これは、何も返さない場合、値undefined
name__の解決されたプロミスを効果的に返すことを意味します。
パーティーに少し遅れましたが、この簡単な解決策はうまくいきました:
function chainError(err) {
return Promise.reject(err)
};
stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);
これにより、チェーンの外でbreakが可能になります。
必要なのは、開始する特別なケースと終了する特別なケースを持つ繰り返しの.then()
チェーンです。
コツは、失敗事例のステップ番号を取得して最終的なエラーハンドラーに波及させることです。
step(1)
を無条件に呼び出します。.then()
を次のコールバックでチェーンします:.then()
をチェーンします。すべてを手書きで書くこともできますが、名前付きの一般化された関数を使用してパターンを示す方が簡単です:
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);});
デモ を参照
step()
では、遅延変数がn
で拒否または解決され、チェーン内の次の.then()
でコールバックがその値を利用できるようになることに注意してください。 stepError
が呼び出されると、エラーはfinalError
によって処理されるまで繰り返しスローされます。
拒否する場合は、拒否エラーを渡す必要があります。次に、拒否を処理するか、チェーンの終わりまで「再スロー」するかをチェックする関数でステップエラーハンドラをラップします。
// 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);
});
コンソールに表示されるもの:
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
ここにいくつかの作業コードがあります https://jsfiddle.net/8hzg5s7m/3/
各ステップに特定の処理がある場合、ラッパーは次のようになります。
/*
* 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);
}
}
あなたのチェーン
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);
});
私が正しく理解していれば、失敗したステップのエラーのみを表示したいでしょうか?
これは、最初の約束の失敗ケースをこれに変更するのと同じくらい簡単です:
step(1).then(function (response) {
step(2);
}, function (response) {
stepError(1);
return response;
}).then( ... )
最初のステップの失敗の場合に$q.reject()
を返すことにより、そのプロミスを拒否し、2番目のthen(...)
でerrorCallbackが呼び出されます。
var 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
または、任意の数のステップで自動化:
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);
});
エラーハンドラーを個別のチェーン要素として直接ステップの実行にアタッチします。
// 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(); });
});
または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(); });
});
注:これは基本的に plumaが答えで示唆している と同じパターンですが、OPの命名を使用しています。
MDNの Promise.prototype.catch()
の例 が非常に役立ちました。
(受け入れられた答えはthen(null, onErrorHandler)
に言及していますが、これは基本的にcatch(onErrorHandler)
と同じです。)
Catchメソッドの使用と連鎖
var p1 = new Promise(function(resolve, reject) { resolve('Success'); }); p1.then(function(value) { console.log(value); // "Success!" throw 'oh, no!'; }).catch(function(e) { console.log(e); // "oh, no!" }).then(function(){ console.log('after a catch the chain is restored'); }, function () { console.log('Not fired due to the catch'); }); // The following behaves the same as above p1.then(function(value) { console.log(value); // "Success!" return Promise.reject('oh, no!'); }).catch(function(e) { console.log(e); // "oh, no!" }).then(function(){ console.log('after a catch the chain is restored'); }, function () { console.log('Not fired due to the catch'); });
エラーを投げるときの落とし穴
// Throwing an error will call the catch method most of the time var p1 = new Promise(function(resolve, reject) { throw 'Uh-oh!'; }); p1.catch(function(e) { console.log(e); // "Uh-oh!" }); // Errors thrown inside asynchronous functions will act like uncaught errors var p2 = new Promise(function(resolve, reject) { setTimeout(function() { throw 'Uncaught Exception!'; }, 1000); }); p2.catch(function(e) { console.log(e); // This is never called }); // Errors thrown after resolve is called will be silenced var p3 = new Promise(function(resolve, reject) { resolve(); throw 'Silenced Exception!'; }); p3.catch(function(e) { console.log(e); // This is never called });
解決したら
//Create a promise which would not call onReject var p1 = Promise.resolve("calling next"); var p2 = p1.catch(function (reason) { //This is never called console.log("catch p1!"); console.log(reason); }); p2.then(function (value) { console.log("next promise's onFulfilled"); /* next promise's onFulfilled */ console.log(value); /* calling next */ }, function (reason) { console.log("next promise's onRejected"); console.log(reason); });
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 []
}
})()
最善の解決策は、ES6 awaitを使用するようにプロミスチェーンにリファクタリングすることです。その後、関数から戻って、残りの動作をスキップできます。
私はこのパターンに一年以上頭を打ち続けており、awaitの使用は天国です。
これを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);
});