AngularJS: Sử dụng lời hứa ở đâu?


141

Tôi đã thấy một số ví dụ về các dịch vụ Đăng nhập Facebook đang sử dụng lời hứa để truy cập API FB Graph.

Ví dụ # 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;
}

Và các dịch vụ được sử dụng "$scope.$digest() // Manual scope evaluation"khi nhận được phản hồi

Ví dụ # 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
        });
    }
}

Câu đố

Các câu hỏi là:

  • Sự khác biệt trong các ví dụ trên là gì?
  • Là gì nguyên nhântrường hợp sử dụng $ q dịch vụ?
  • Và nó hoạt động như thế nào?

9
Nghe có vẻ như bạn nên đọc về những lời hứa và lý do tại sao chúng được sử dụng nói chung ... chúng không dành riêng cho góc cạnh và có rất nhiều tài liệu có sẵn
charlietfl 24/03/13

1
@charlietfl, điểm tốt, nhưng tôi mong đợi một câu trả lời phức tạp sẽ bao gồm cả hai: tại sao chúng được sử dụng nói chung và cách sử dụng nó trong Angular. Cảm ơn lời đề nghị của bạn
Maksym

Câu trả lời:


401

Đây sẽ không phải là một câu trả lời hoàn chỉnh cho câu hỏi của bạn, nhưng hy vọng điều này sẽ giúp bạn và những người khác khi bạn cố gắng đọc tài liệu về $qdịch vụ. Phải mất một thời gian để hiểu nó.

Hãy tạm gác AngularJS một chút và chỉ xem xét các lệnh gọi API của Facebook. Cả hai lệnh gọi API đều sử dụng cơ chế gọi lại để thông báo cho người gọi khi có phản hồi từ Facebook:

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

Đây là một mẫu chuẩn để xử lý các hoạt động không đồng bộ trong JavaScript và các ngôn ngữ khác.

Một vấn đề lớn với mẫu này phát sinh khi bạn cần thực hiện một chuỗi các hoạt động không đồng bộ, trong đó mỗi hoạt động liên tiếp phụ thuộc vào kết quả của hoạt động trước đó. Đó là những gì mã này đang làm:

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

Đầu tiên, nó cố gắng đăng nhập và sau đó chỉ sau khi xác minh rằng đăng nhập thành công, nó mới thực hiện yêu cầu đối với API đồ thị.

Ngay cả trong trường hợp này, chỉ kết hợp hai thao tác, mọi thứ bắt đầu trở nên lộn xộn. Phương pháp askFacebookForAuthenticationchấp nhận một cuộc gọi lại cho thất bại và thành công, nhưng điều gì xảy ra khi FB.loginthành công nhưng FB.apithất bại? Phương thức này luôn gọi hàm successgọi lại bất kể kết quả của FB.apiphương thức.

Bây giờ hãy tưởng tượng rằng bạn đang cố mã hóa một chuỗi mạnh mẽ gồm ba hoặc nhiều thao tác không đồng bộ, theo cách xử lý đúng các lỗi ở mỗi bước và sẽ dễ đọc đối với bất kỳ ai khác hoặc thậm chí với bạn sau vài tuần. Có thể, nhưng rất dễ dàng để tiếp tục lồng các cuộc gọi lại đó và mất dấu vết trên đường đi.

Bây giờ, hãy tạm gác API Facebook sang một bên và chỉ xem xét API Lời hứa góc cạnh, như được triển khai bởi $qdịch vụ. Mẫu được triển khai bởi dịch vụ này là một nỗ lực để biến lập trình không đồng bộ trở lại thành một thứ giống như một chuỗi các câu lệnh đơn giản tuyến tính, với khả năng 'ném' một lỗi ở bất kỳ bước nào và xử lý nó ở cuối, tương tự về mặt ngữ nghĩa với try/catchkhối quen thuộc .

Hãy xem xét ví dụ giả định này. Giả sử chúng ta có hai hàm, trong đó hàm thứ hai tiêu thụ kết quả của hàm thứ nhất:

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

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

 secondFn(firstFn()); 

Bây giờ hãy tưởng tượng rằng FirstFn và secondFn đều mất nhiều thời gian để hoàn thành, vì vậy chúng tôi muốn xử lý chuỗi này không đồng bộ. Đầu tiên chúng ta tạo một deferredđối tượng mới , đại diện cho một chuỗi các hoạt động:

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

Tài promisesản đại diện cho kết quả cuối cùng của chuỗi. Nếu bạn đăng nhập một lời hứa ngay sau khi tạo nó, bạn sẽ thấy rằng đó chỉ là một đối tượng trống ( {}). Không có gì để xem, di chuyển ngay dọc.

Cho đến nay, lời hứa của chúng tôi chỉ đại diện cho điểm khởi đầu trong chuỗi. Bây giờ hãy thêm hai hoạt động của chúng tôi:

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

Các thenphương pháp thêm một bước để chuỗi và sau đó trả về một lời hứa mới đại diện cho kết quả cuối cùng của chuỗi mở rộng. Bạn có thể thêm bao nhiêu bước tùy thích.

Cho đến nay, chúng tôi đã thiết lập chuỗi chức năng của mình, nhưng không có gì thực sự xảy ra. Bạn bắt đầu mọi thứ bằng cách gọi deferred.resolve, chỉ định giá trị ban đầu bạn muốn chuyển đến bước thực tế đầu tiên trong chuỗi:

 deferred.resolve('initial value');

Và rồi ... vẫn không có gì xảy ra. Để đảm bảo rằng các thay đổi mô hình được quan sát đúng cách, Angular không thực sự gọi bước đầu tiên trong chuỗi cho đến lần tiếp theo $applyđược gọi:

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

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

Vậy xử lý lỗi thì sao? Cho đến nay chúng tôi chỉ chỉ định một trình xử lý thành công ở mỗi bước trong chuỗi. thencũng chấp nhận một trình xử lý lỗi như một đối số thứ hai tùy chọn. Đây là một ví dụ khác, dài hơn về chuỗi lời hứa, lần này có xử lý lỗi:

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

Như bạn có thể thấy trong ví dụ này, mỗi trình xử lý trong chuỗi có cơ hội chuyển hướng lưu lượng truy cập sang trình xử lý lỗi tiếp theo thay vì trình xử lý thành công tiếp theo . Trong hầu hết các trường hợp, bạn có thể có một trình xử lý lỗi duy nhất ở cuối chuỗi, nhưng bạn cũng có thể có các trình xử lý lỗi trung gian cố gắng phục hồi.

Để nhanh chóng quay lại ví dụ của bạn (và câu hỏi của bạn), tôi sẽ chỉ nói rằng chúng đại diện cho hai cách khác nhau để điều chỉnh API hướng gọi lại của Facebook theo cách quan sát các thay đổi mô hình của Angular. Ví dụ đầu tiên kết thúc lệnh gọi API trong một lời hứa, có thể được thêm vào một phạm vi và được hiểu bởi hệ thống tạo khuôn mẫu của Angular. Cách thứ hai sử dụng cách tiếp cận mạnh mẽ hơn để đặt kết quả gọi lại trực tiếp trên phạm vi, sau đó gọi $scope.$digest()để làm cho Angular nhận thức được sự thay đổi từ nguồn bên ngoài.

Hai ví dụ không thể so sánh trực tiếp, vì lần đầu tiên thiếu bước đăng nhập. Tuy nhiên, nói chung, mong muốn đóng gói các tương tác với các API bên ngoài như thế này trong các dịch vụ riêng biệt và cung cấp kết quả cho các bộ điều khiển như đã hứa. Bằng cách đó, bạn có thể tách bộ điều khiển của mình khỏi các mối quan tâm bên ngoài và kiểm tra chúng dễ dàng hơn với các dịch vụ giả.


5
Tôi nghĩ đó là câu trả lời tuyệt vời! Điều chính, đối với tôi, là mô tả trường hợp chung khi lời hứa thực sự là thực tế. Thành thật tôi đã hy vọng cho một ví dụ thực tế khác (như với Facebook), nhưng điều này cũng hoạt động tôi đoán. Cảm ơn nhiều!
Maksym

2
Một cách khác để xâu chuỗi nhiều thenphương pháp là sử dụng $q.all. Một hướng dẫn nhanh về điều đó có thể được tìm thấy ở đây .
Bogdan

2
$q.alllà phù hợp nếu bạn cần đợi nhiều hoạt động không đồng bộ độc lập hoàn thành. Nó không thay thế chuỗi nếu mỗi hoạt động phụ thuộc vào kết quả của hoạt động trước đó.
karlgold

1
chuỗi của sau đó được giải thích ngắn gọn ở đây. Đã giúp tôi hiểu và sử dụng nó hết tiềm năng của nó. Cảm ơn
Tushar Joshi

1
Câu trả lời tuyệt vời @karlgold! Tôi có một câu hỏi. Nếu, trong đoạn mã cuối cùng, bạn thay đổi return 'firstResult'phần thành return $q.resolve('firstResult'), sự khác biệt sẽ là gì?
Technophyle

9

Tôi mong đợi một câu trả lời phức tạp sẽ bao gồm cả hai: tại sao chúng được sử dụng chung và làm thế nào để sử dụng nó trong Angular

Đây là phần mềm cho lời hứa góc cạnh MVP (lời hứa khả thi tối thiểu) : http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview

Nguồn:

(đối với những người quá lười biếng để nhấp vào liên kết)

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

  });

(Tôi biết nó không giải quyết ví dụ Facebook cụ thể của bạn nhưng tôi thấy đoạn trích sau hữu ích)

Thông qua: http://markdalgleish.com/2013/06/USE-promises-in-angularjs-view/


Cập nhật ngày 28 tháng 2 năm 2014: Kể từ ngày 1.2.0, các lời hứa không còn được giải quyết bằng các mẫu. http: //www.ben Meat.com/2013/02/angularjs-creating-service-with-http.html

(ví dụ plunker sử dụng 1.1.5.)


afaik chúng tôi yêu như vậy bởi vì chúng tôi lười biếng
mkb

điều này giúp tôi hiểu $ q, hoãn lại và xâu chuỗi các cuộc gọi chức năng, vì vậy cảm ơn.
aliopi

1

Một hoãn lại đại diện cho kết quả của một hoạt động không điển hình. Nó hiển thị một giao diện có thể được sử dụng để báo hiệu trạng thái và kết quả của hoạt động mà nó đại diện. Nó cũng cung cấp một cách để có được ví dụ lời hứa liên quan.

Một lời hứa cung cấp giao diện để tương tác với nó được hoãn lại, và do đó, cho phép các bên quan tâm có quyền truy cập vào trạng thái và kết quả của hoạt động hoãn lại.

Khi tạo hoãn lại, trạng thái của nó đang chờ xử lý và nó không có kết quả. Khi chúng tôi giải quyết () hoặc từ chối () hoãn lại, nó sẽ thay đổi trạng thái thành giải quyết hoặc từ chối. Tuy nhiên, chúng ta có thể nhận được lời hứa liên quan ngay lập tức sau khi tạo một tương tác hoãn lại và thậm chí chỉ định các tương tác với kết quả trong tương lai. Những tương tác đó sẽ chỉ xảy ra sau khi bị từ chối hoặc giải quyết.


1

sử dụng lời hứa trong bộ điều khiển và đảm bảo dữ liệu có sẵn hay không

 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>

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.