AngularJS:promiseはどこで使用しますか?


141

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

例#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サービスを使用する理由ケースは何ですか?
  • そして、どのようにそれはない仕事しますか

9
あなたは約束が何であるか、そしてそれらが一般になぜ使われるのかについて読んでおく必要があるように聞こえます...それらは角度に限定されず、利用可能な材料がたくさんあります
charlietfl

1
@charlietfl、良い点ですが、私は両方をカバーする複雑な答えを期待しました:なぜそれらが一般的に使用されるのか、そしてそれをAngularでどのように使用するのか。あなたの提案をありがとう
Maksym 2013

回答:


401

これはあなたの質問に対する完全な答えになることはありませんが、うまくいけば、これが$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.');
      }
  });

最初にログインを試み、次にログインが成功したことを確認した後にのみ、グラフAPIにリクエストを送信します。

この場合、2つの操作をつなぐだけの場合でも、状況は複雑になります。メソッドaskFacebookForAuthenticationは失敗と成功のコールバックを受け入れますが、FB.login成功したがFB.api失敗した場合はどうなりますか?このメソッドsuccessは、FB.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を作成した直後にログに記録すると、それが空のオブジェクト({})であることがわかります。まだ見るものはありません。

これまでのところ、私たちの約束はチェーンの出発点を表すにすぎません。次に、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番目の引数としてエラーハンドラを受け入れます。次に、もう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);

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

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

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


5
いい答えだと思います!私にとっての主なことは、約束が実際に現実的である一般的なケースを説明することでした。正直なところ、私は別の実際の例(Facebookのように)を望んでいましたが、これでもうまくいくと思います。どうもありがとう!
Maksym 2013

2
複数のthenメソッドをチェーンする代わりに、を使用することもできます$q.all。その簡単なチュートリアルはここにあります
ボグダン2014

2
$q.all複数の独立した非同期操作が完了するのを待つ必要がある場合に適しています。各操作が前の操作の結果に依存している場合、チェーンの代わりにはなりません。
karlgold 14

1
そのときの連鎖はここで簡潔に説明されています。それを理解し、その潜在能力を最大限に活用するのに役立ちました。ありがとう
Tushar Joshi 2015

1
すばらしい回答@karlgold!一つ質問があります。最後のコードスニペットでreturn 'firstResult'パーツをreturn $q.resolve('firstResult')に変更した場合、何が違うのでしょうか?
technophyle 2015年

9

私は両方をカバーする複雑な答えを期待しました:それらが一般的に使用される理由とAngularでそれを使用する方法

これは、角度付き約束MVP (実行可能な最小約束)の要点です:http : //plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

ソース:

(リンクをクリックするのが面倒な人のために)

index.html

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
    <script src="app.js"></script>
  </head>

  <body ng-app="myModule" ng-controller="HelloCtrl">
    <h1>Messages</h1>
    <ul>
      <li ng-repeat="message in messages">{{ message }}</li>
    </ul>
  </body>

</html>

app.js

angular.module('myModule', [])

  .factory('HelloWorld', function($q, $timeout) {

    var getMessages = function() {
      var deferred = $q.defer();

      $timeout(function() {
        deferred.resolve(['Hello', 'world']);
      }, 2000);

      return deferred.promise;
    };

    return {
      getMessages: getMessages
    };

  })

  .controller('HelloCtrl', function($scope, HelloWorld) {

    $scope.messages = HelloWorld.getMessages();

  });

(私はそれがあなたの特定のFacebookの例を解決しないことを知っていますが、私は以下のスニペットが役立つと思います)

経由:http : //markdalgleish.com/2013/06/using-promises-in-angularjs-views/


2014年2月28日の更新: 1.2.0以降、プロミスはテンプレートによって解決されなくなりました。 http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(プランカーの例では1.1.5を使用します。)


私たちは怠惰だから私たちは大好きです
mkb

これは、$ q、据え置きおよびチェーンされた.then関数呼び出しを理解するのに役立ちました。ありがとうございます。
aliopi 2017

1

据え置きは、非同期操作の結果を表します。これは、状態とそれが表す操作の結果を通知するために使用できるインターフェースを公開します。また、関連するpromiseインスタンスを取得する方法も提供します。

promiseは、関連する据え置きと対話するためのインターフェースを提供します。そのため、関係者は、据え置き操作の状態と結果にアクセスできます。

遅延オブジェクトを作成するとき、その状態は保留中であり、結果はありません。遅延オブジェクトをresolve()またはreject()すると、状態が解決または拒否されます。それでも、遅延を作成した直後に関連するプロミスを取得し、将来の結果との相互作用を割り当てることさえできます。これらの相互作用は、据え置きが拒否または解決された後にのみ発生します。


1

コントローラ内でpromiseを使用し、データが利用可能かどうかを確認します

 var app = angular.module("app",[]);
      
      app.controller("test",function($scope,$q){
        var deferred = $q.defer();
        deferred.resolve("Hi");
        deferred.promise.then(function(data){
        console.log(data);    
        })
      });
      angular.bootstrap(document,["app"]);
<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
  </head>

  <body>
    <h1>Hello Angular</h1>
    <div ng-controller="test">
    </div>
  </body>

</html>

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.