1. ホーム
  2. angularjs

[解決済み] AngularJS : プロミスってどこで使うの?

2022-05-15 15:20:57

質問

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 サービスを利用できますか?
  • また、どのように は働きます。 ?

どのように解決するのですか?

これはあなたの質問に対する完全な答えになるわけではありませんが、これがあなたや他の人が $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などで非同期処理を行う際の標準的なパターンです。

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

  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 は失敗するのか? このメソッドは常に success の結果に関係なく、コールバックを呼び出します。 FB.api メソッドの結果に関わらず

ここで、3つ以上の非同期操作の堅牢なシーケンスを、各ステップで適切にエラーを処理し、数週間後に他の人やあなたにとってさえ読みやすくなる方法でコーディングしようとしていると想像してみてください。 可能ですが、コールバックをネストし続けるだけで、途中でエラーを見失うことは非常に簡単です。

さて、Facebook APIを少し脇に置いて、Angular Promises APIを考えてみましょう。 $q サービスによって実装されています。 このサービスで実装されているパターンは、非同期プログラミングを一連の単純なステートメントに似たものに戻す試みで、途中の任意のステップでエラーを「スロー」して最後にそれを処理する機能があり、意味的にはおなじみの try/catch ブロックにセマンティックに似ています。

次のような例を考えてみましょう。 2つの関数があり、2つ目の関数が1つ目の関数の結果を消費するとします。

 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 プロパティはチェーンの最終的な結果を表します。 作成直後のプロミスをログに記録すると、単なる空のオブジェクト( {} ). まだ何も見るべきものはありません、すぐに移動してください。

これまでのところ、私たちのプロミスはチェーンの出発点を表しているだけです。 では、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引数としてエラーハンドラも受け取ります。 もう一つの長いプロミス・チェーンの例です。今回はエラー処理付きです。

 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);

この例でわかるように、チェーン内の各ハンドラは、トラフィックを次の エラー ハンドラではなく、次の 成功 ハンドラに置き換えます。 ほとんどの場合、チェーンの末端に単一のエラーハンドラを持つことができますが、回復を試みる中間エラーハンドラを持つこともできます。

あなたの例(と質問)に素早く戻るために、私は、それらがFacebookのコールバック指向のAPIをAngularのモデル変更を観察する方法に適応させる2つの異なる方法を表しているとだけ言っておきます。 最初の例は、APIコールをプロミスでラップしています。これはスコープに追加することができ、Angularのテンプレートシステムによって理解されます。 2つ目はより強引な方法で、コールバックの結果を直接スコープに設定し、そのスコープで $scope.$digest() を呼び出して、Angularに外部ソースからの変更を認識させます。

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