AngularJS : プロミスはどこで使うのか? 質問する

AngularJS : プロミスはどこで使うのか? 質問する

Facebookログインサービスの例をいくつか見ました。約束FB Graph API にアクセスします。

例 1:

this.api = function(item) {
  var deferred = $q.defer();
  if (item) {
    facebook.FB.api('/' + item, function (result) {
      $rootScope.$apply(function () {
        if (angular.isUndefined(result.error)) {
          deferred.resolve(result);
        } else {
          deferred.reject(result.error);
        }
      });
    });
  }
  return deferred.promise;
}

"$scope.$digest() // Manual scope evaluation"そして、応答を得たときに使用したサービス

例2:

angular.module('HomePageModule', []).factory('facebookConnect', function() {
    return new function() {
        this.askFacebookForAuthentication = function(fail, success) {
            FB.login(function(response) {
                if (response.authResponse) {
                    FB.api('/me', success);
                } else {
                    fail('User cancelled login or did not fully authorize.');
                }
            });
        }
    }
});

function ConnectCtrl(facebookConnect, $scope, $resource) {

    $scope.user = {}
    $scope.error = null;

    $scope.registerWithFacebook = function() {
        facebookConnect.askFacebookForAuthentication(
        function(reason) { // fail
            $scope.error = reason;
        }, function(user) { // success
            $scope.user = user
            $scope.$digest() // Manual scope evaluation
        });
    }
}

JSFiddle

質問は次のとおりです:

  • 何ですか違い上記の例では?
  • 何ですか理由そして事例使用する$qサービス?
  • そしてそれはどのように仕事?

ベストアンサー1

これはあなたの質問に対する完全な答えではありませんが、サービスに関するドキュメントを読むときに、あなたや他の人の役に立つことを願っています$q。理解するのに少し時間がかかりました。

AngularJSを少し脇に置いて、Facebook API呼び出しだけを考えてみましょう。どちらのAPI呼び出しも折り返し電話Facebook からの応答が利用可能になったときに発信者に通知するメカニズム:

  facebook.FB.api('/' + item, function (result) {
    if (result.error) {
      // handle error
    } else {
      // handle success
    }
  });
  // program continues while request is pending
  ...

これは、JavaScript やその他の言語で非同期操作を処理するための標準パターンです。

このパターンの大きな問題は、一連の非同期操作を実行する必要がある場合に発生します。この場合、各後続操作は前の操作の結果に依存します。次のコードではそれが実行されています。

  FB.login(function(response) {
      if (response.authResponse) {
          FB.api('/me', success);
      } else {
          fail('User cancelled login or did not fully authorize.');
      }
  });

まずログインを試み、ログインが成功したことを確認した後でのみ、Graph API にリクエストを送信します。

2 つの操作を連鎖させるだけのこの場合でも、状況は複雑になり始めます。メソッドはaskFacebookForAuthentication失敗と成功のコールバックを受け入れますが、FB.login成功しても失敗した場合はどうなるでしょうか。このメソッドは、メソッドの結果に関係なく、FB.api常にコールバックを呼び出します。successFB.api

ここで、各ステップでエラーを適切に処理し、数週間後には他の人や自分自身にも判読可能な方法で、3 つ以上の非同期操作の堅牢なシーケンスをコーディングしようとしていると想像してください。可能ですが、コールバックをネストし続けると、途中でエラーが追跡されなくなる可能性が非常に高くなります。

さて、Facebook API についてはしばらく脇に置いて、$qサービスによって実装されている Angular Promises API について考えてみましょう。このサービスによって実装されているパターンは、非同期プログラミングを、途中のどの段階でもエラーを「スロー」して最後に処理する機能を備えた、単純なステートメントの線形シリーズに似たものに戻そうとする試みであり、意味的にはおなじみのtry/catchブロックに似ています。

この不自然な例を考えてみましょう。2 つの関数があり、2 番目の関数が最初の関数の結果を使用するとします。

 var firstFn = function(param) {
    // do something with param
    return 'firstResult';
 };

 var secondFn = function(param) {
    // do something with param
    return 'secondResult';
 };

 secondFn(firstFn()); 

ここで、firstFn と secondFn の両方が完了するまでに長い時間がかかると仮定し、このシーケンスを非同期的に処理します。まず、deferred一連の操作を表す新しいオブジェクトを作成します。

 var deferred = $q.defer();
 var promise = deferred.promise;

プロパティpromiseは、チェーンの最終的な結果を表します。作成後すぐに Promise をログに記録すると、空のオブジェクト ( {}) であることがわかります。まだ何も表示されませんので、先に進んでください。

これまでのところ、Promise はチェーンの開始点のみを表しています。次に、2 つの操作を追加しましょう。

 promise = promise.then(firstFn).then(secondFn);

このthenメソッドはチェーンにステップを追加し、拡張されたチェーンの最終結果を表す新しいプロミスを返します。ステップは好きなだけ追加できます。

ここまでで関数のチェーンを設定しましたが、実際には何も起こりませんでした。 を呼び出してdeferred.resolve、チェーンの最初の実際のステップに渡す初期値を指定することで、処理を開始します。

 deferred.resolve('initial value');

そして...まだ何も起こりません。モデルの変更が適切に監視されるようにするために、Angular は次に$apply呼び出されるまでチェーンの最初のステップを実際には呼び出しません。

 deferred.resolve('initial value');
 $rootScope.$apply();

 // or     
 $rootScope.$apply(function() {
    deferred.resolve('initial value');
 });

ではエラー処理はどうでしょうか?これまでは成功ハンドラチェーンの各ステップで、thenエラー ハンドラをオプションの 2 番目の引数として受け入れます。次は、エラー処理を含む、Promise チェーンのもう 1 つの長い例です。

 var firstFn = function(param) {
    // do something with param
    if (param == 'bad value') {
      return $q.reject('invalid value');
    } else {
      return 'firstResult';
    }
 };

 var secondFn = function(param) {
    // do something with param
    if (param == 'bad value') {
      return $q.reject('invalid value');
    } else {
      return 'secondResult';
    }
 };

 var thirdFn = function(param) {
    // do something with param
    return 'thirdResult';
 };

 var errorFn = function(message) {
   // handle error
 };

 var deferred = $q.defer();
 var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);

この例からわかるように、チェーン内の各ハンドラはトラフィックを次のハンドラに転送する機会を持っています。エラー次のハンドラの代わりに成功ハンドラ。ほとんどの場合、チェーンの最後に 1 つのエラー ハンドラを配置できますが、回復を試みる中間エラー ハンドラを配置することもできます。

例 (および質問) に簡単に戻ると、これらは Facebook のコールバック指向 API を Angular のモデル変更の監視方法に適合させる 2 つの異なる方法を表しているとだけ言っておきます。最初の例では、API 呼び出しをプロミスでラップします。これはスコープに追加でき、Angular のテンプレート システムによって認識されます。2 番目の例では、コールバック結果をスコープに直接設定し、呼び出して$scope.$digest()外部ソースからの変更を Angular に認識させるという、より強引なアプローチを採用しています。

最初の例にはログイン手順がないため、2 つの例を直接比較することはできません。ただし、一般的には、このような外部 API とのやり取りを別のサービスにカプセル化し、結果をプロミスとしてコントローラーに渡すことが望ましいです。こうすることで、コントローラーを外部の問題から分離し、モック サービスを使用してより簡単にテストすることができます。

おすすめ記事