次のコードでは、$ q promiseのcatch関数によって例外がキャッチされます。
// Fiddle - http://jsfiddle.net/EFpn8/6/
f1().then(function(data) {
console.log("success 1: "+data)
return f2();
})
.then(function(data) {console.log("success 2: "+data)})
.catch(function(data) {console.log("error: "+data)});
function f1() {
var deferred = $q.defer();
// An exception thrown here is not caught in catch
// throw "err";
deferred.resolve("done f1");
return deferred.promise;
}
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
ただし、コンソールログ出力を見ると、次のことがわかります。
例外はAngularでキャッチされましたが、ブラウザのエラー処理によってもキャッチされました。この動作はQライブラリで再現されます。
バグですか? $ qで本当に例外をキャッチするにはどうすればよいですか?
この動作の理由は、たとえばプログラミングエラーが原因である可能性があるため、キャッチされなかったエラーは通常の拒否とは異なるためです。実際には、ネイティブのPromiseも他の一般的なPromiseライブラリも、スローされたエラーと通常の拒否を区別しないため、これはユーザーにとって混乱または望ましくないことが判明しました。 (注:この動作はPromises/A +仕様に反するものではありませんが、規定されていません。)
$ q:
e13eea のため、promiseの
onFulfilled
またはonRejection
ハンドラーからスローされたエラーは、通常の拒否とまったく同じように扱われます。以前は、それは$exceptionHandler()
にも渡されていました(理由としてエラーを使用してpromiseを拒否することに加えて)。新しい動作は、_
$q
_に依存するすべてのサービス/コントローラー/フィルターなどに適用されます(_$http
_や_$route
_などの組み込みサービスを含む)。たとえば、_$http's transformRequest/Response
_関数またはルートのredirectTo関数、およびルートのresolveオブジェクトで指定された関数は、エラーをスローした場合に$exceptionHandler()
を呼び出さなくなりました。それ以外は、すべてが同じように動作し続けます。つまり、プロミスは拒否され、ルートの移行はキャンセルされ、_$routeChangeError
_イベントがブロードキャストされます。
Angularの$q
は、スローされたエラーがログに記録される規則を使用します関係なくキャッチされます。代わりに、拒否を通知する場合は、次のようにreturn $q.reject(...
する必要があります。
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
return $q.reject(new Error("err"));//throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
これは、SyntaxErrorのようなエラーから拒否を区別するためです。個人的には、これは私が同意しない設計上の選択ですが、$q
が小さいため、信頼できる未処理の拒否検出メカニズムを実際に組み込むことはできないため、理解できます。 Bluebirdのような強力なライブラリでは、この種のことは必要ありません。
補足として-決して文字列をスローしないでください:そのようにスタックトレースを見逃します。
バグですか?
いいえ。 $ qのソース を見ると、コールバックでスローされた例外に応答するために、意図的なtry/catchブロックが作成されていることがわかります。
deferred.reject
に電話したように、約束を拒否する...ブラウザのエラー処理にも巻き込まれました
明確にするために、例外はブラウザによって直接処理されませんが、Angularがconsole.error
を呼び出したため、エラーとして表示されます
$ qで本当に例外をキャッチするにはどうすればよいですか?
コールバックは、現在の呼び出しスタックがクリアされたときに実行されるため、外部関数をtry
/catch
ブロックでラップすることはできません。ただし、2つのオプションがあります。
コールバック内で、例外をスローする可能性のあるコードの周囲にtry
/catch
ブロックを配置します。
f1().then(function(data) {
try {
return f2();
} catch(e) {
// Might want convert exception to rejected promise
return $q.reject(e);
}
})
$ exceptionHandler実装をオーバーライドする方法 のように、Angularの$exceptionHandler
サービスの動作を変更します。まったく何もしないように変更できる可能性があるため、コンソールのエラーログには何も記録されませんが、お勧めすることはないと思います。
Deferredは、promiseを構築するための時代遅れで本当にひどい方法であり、コンストラクターを使用すると、この問題などが解決されます。
// This function is guaranteed to fulfill the promise contract
// of never throwing a synchronous exception, using deferreds manually
// this is virtually impossible to get right
function f1() {
return new Promise(function(resolve, reject) {
// code
});
}
angular promisesが上記をサポートしているかどうかはわかりませんが、そうでない場合は、次のように実行できます。
function createPromise(fn) {
var d = $q.defer();
try {
fn(d.resolve.bind(d), d.reject.bind(d));
}
catch (e) {
d.reject(e);
}
return d.promise;
}
使用法はpromiseコンストラクターと同じです。
function f1() {
return createPromise(function(resolve, reject){
// code
});
}
これは、新しい$ q構築関数、.finally()の使用、拒否、およびpromiseチェーンの伝播を示すサンプルテストです。
iit('test',inject(function($q, $timeout){
var finallyCalled = false;
var failValue;
var promise1 = $q.when(true)
.then(function(){
return $q(function(resolve,reject){
// Reject promise1
reject("failed");
});
})
.finally(function(){
// Always called...
finallyCalled = true;
// This will be ignored
return $q.when('passed');
});
var promise2 = $q.when(promise1)
.catch(function(value){
// Catch reject of promise1
failValue = value;
// Continue propagation as resolved
return value+1;
// Or continue propagation as rejected
//return $q.reject(value+2);
});
var updateFailValue = function(val){ failValue = val; };
$q.when(promise2)
.then( updateFailValue )
.catch(updateFailValue );
$timeout.flush();
expect( finallyCalled ).toBe(true);
expect( failValue ).toBe('failed1');
}));