
ここに、あなたが知らなかったかもしれないいくつかのポイントがあります。
1.その後、常に新しいプロミスを返します
例を見てみましょう:
function asyncFunction() { var deferred = $q.defer(); doSomethingAsync().then(function(res) { res = asyncManipulate(res); deferred.resolve(res); }, function(err) { deferred.reject(err); }); return deferred.promise; }
ここで
$q.defer()
新しい約束が無意味に作成されます。 コードの作者は、それから約束を返すことを明らかに知りませんでした。 コードを改善するには、thenの結果を返すだけです:
function asyncFunction() { return doSomethingAsync().then(function(res) { return asyncManipulate(res); }); }
2.約束の結果は「失われない」
再び例:
function asyncFunction() { return doSomethingAsync().then(function(res) { return asyncManipulate(res); }, function(err) { return $q.reject(err); }); }
doSomethingAsync
関数
doSomethingAsync
結果は、解決または拒否に関係なく、独自のハンドラーが見つかるまで(ポップアップハンドラーが存在する場合)「ポップアップ」します。 これは、結果を処理する必要がない場合、対応するハンドラーを単純に省略できることを意味します。これは、結果がどこにも消えず、そのまま続くためです。 この例では、操作が実行されないため、2番目のハンドラーを安全に削除できます(拒否処理)。
function asyncFunction() { return doSomethingAsync().then(function(res) { return asyncManipulate(res); }); }
リジェクトケースのみを処理する場合は、解決処理を省略することもできます。
function asyncFunction() { return doSomethingAsync().then(null, function(err) { return errorHandler(err); }); }
ところで、そのような場合には、構文糖衣があります:
function asyncFunction() { return doSomethingAsync().catch(function(err) { return errorHandler(err); }); }
3. $ q.reject()を返すことによってのみ拒否ハンドラーにアクセスできます
コード:
asyncFunction().then(function (res) { // some code return res; }, function (res) { // some code }).then(function (res) { console.log('in resolve'); }, function (res) { console.log('in reject'); });
この例では、
asyncFunction
関数がどのように
asyncFunction
しても、コンソールには「in resolve」が表示されます。 これは、リジェクトハンドラーに戻る方法が1つしかないためです-return $ q.reject()。 それ以外の場合、解決ハンドラーが呼び出されます。
asyncFunction
がリジェクトを返す場合、コンソールに「in reject」が表示されるようにコードを書き直します。
asyncFunction().then(function (res) { // some code return res; }, function (res) { // some code return $q.reject(res); }).then(function (res) { console.log('in resolve'); }, function (res) { console.log('in reject'); });
4.最終的に約束の結果を変更しません
asyncFunction().then(function (res) { importantFunction(); return res; }, function (err) { importantFunction(); return $q.reject(err); }).then(function (res) { // some resolve code }, function (err) { // some reject code })
promiseの結果に関係なくコードを実行する必要がある場合は、常に呼び出されるfinallyハンドラーを使用します。 また、finallyブロックは、結果のプロミスのタイプを変更しないため、以降の処理には影響しません。 改善:
asyncFunction().finally(function () { importantFunction(); }).then(function (res) { // some resolve code }, function (err) { // some reject code })
finallyハンドラーが$ q.reject()を返す場合、次に拒否ハンドラーが呼び出されます。 解決ハンドラーを呼び出す方法は保証されていません。
5. $ q.allは機能を並行して実行します
ネストされた呼び出しチェーンを検討してください。
loadSomeInfo().then(function(something) { loadAnotherInfo().then(function(another) { doSomethingOnThem(something, another); }); });
loadAnotherInfo
、
loadAnotherInfo
と
loadAnotherInfo
両方を実行した結果が必要です。 そして、それらがどの順序で呼び出されるかは重要ではありません。両方の関数からの結果が受信された後に
doSomethingOnThem
関数が呼び出されることが重要です。 つまり、これらの関数は並行して呼び出すことができます。 しかし、このコードの作成者は、明らかに$ q.allメソッドについて知りませんでした。 書き直します:
$q.all([loadSomeInfo(), loadAnotherInfo()]).then(function (results) { doSomethingOnThem(results[0], results[1]); });
$ q.allは、並列に実行される関数の配列を受け入れます。 $ q.allによって返されるpromiseは、配列内のすべての関数が完了すると呼び出されます。 結果はすべての関数の結果を含む
results
配列として利用できます。
したがって、非同期関数の実行を同期する必要がある場合は、$ q.allメソッドを使用する必要があります。
6. $ q.whenがすべてを約束に変える
コードが非同期関数に依存し、同期関数に依存する場合があります。 次に、同期関数のラッパーを作成して、コードの順序を維持します。
var promise; if (isAsync){ promise = asyncFunction(); } else { var localPromise = $q.defer(); promise = localPromise.promise; localPromise.resolve(42); } promise.then(function (res) { // some code });
このコードには何も問題はありません。 しかし、それをきれいにする方法があります:
$q.when(isAsync? asyncFunction(): 42).then(function (res) { // some code });
$ q.whenは、promiseまたは通常の値のいずれかを受け入れ、常にpromiseを返す一種のプロキシ関数です。
7. Promiseの正しいエラー処理
非同期関数でのエラー処理の例を見てみましょう。
function asyncFunction(){ return $timeout(function meAsynk(){ throw new Error('error in meAsynk'); }, 1); } try{ asyncFunction(); } catch(err){ errorHandler(err); }
ここに問題がありますか? try / catchブロックは、
asyncFunction
関数の
asyncFunction
時に発生するエラーのみをキャッチします。 ただし、
$timeout
がコールバック関数
meAsynk
起動すると、そこで発生するすべてのエラーは、キャッチされていないアプリケーションエラーのハンドラー(アプリケーションのキャッチされていない例外ハンドラー)に分類されます。 したがって、キャッチハンドラーは何も認識しません。
したがって、try / catchで非同期関数をラップすることは役に立ちません。 しかし、そのような状況で何をすべきか? これを行うには、非同期関数にエラーを処理する特別なコールバックが必要です。 $ qでは、そのようなハンドラーは拒否ハンドラーです。
エラーがハンドラーに表示されるようにコードをやり直します(上記の
catch
シュガーを使用します)。
function asyncFunction(){ return $timeout(function meAsynk(){ throw new Error('error in meAsynk'); }, 1); } asyncFunction().catch(function (err) { errorHandler(err); });
別の例を考えてみましょう:
function asyncFunction() { var promise = doSomethingAsync(); promise.then(function() { return somethingAsyncAgain(); }); return promise; }
このコードには1つの問題があります。somethingAsyncAgain関数がrejectを返した場合(そして、既にわかっているように、エラーが発生した場合はrejectも呼び出されます)、関数を呼び出したコードはそれを認識しません。 約束は一貫している必要があり、以下はそれぞれ前のものに依存する必要があります。 しかし、この例では、約束は破られています。 それを修正するために、次のように書き直します。
function asyncFunction() { return doSomethingAsync().then(function() { return somethingAsyncAgain(); }); }
これで、関数を呼び出すコードは、
somethingAsyncAgain
関数の実行結果に完全に依存し、すべてのエラーは上位のコードで処理できます。
この例を見てみましょう:
asyncFunction().then( function() { return somethingElseAsync(); }, function(err) { errorHandler(err); });
今回はすべてが正しいようです。 ただし、エラーが関数
somethingElseAsync
該当する場合、誰にも処理されません。 リジェクトハンドラーが分離されるようにコードを書き直します。
asyncFunction().then(function() { return somethingElseAsync(); }).catch(function(err) { errorHandler(err); });
これで、発生したエラーが処理されます。
PS
$ qサービスは、 Promises / A +標準の実装です。 より深く理解するために、この標準を読むことをお勧めします。
また、jQueryでのpromiseの実装がPromises / A +標準とは異なることも注目に値します。 これらの違いに興味がある人は、 この記事に慣れることができます 。