AngularJS: Где использовать promises?

Я увидел несколько примеров служб входа в Facebook, которые использовали promises для доступа к API графиков FB.

Пример # 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 на мгновение и просто рассмотрите вызовы API Facebook. Оба вызова 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.');
      }
  });

Сначала он пытается войти в систему, а затем только после проверки успешного входа в систему делает запрос API Graph.

Даже в этом случае, который объединяет только две операции, все начинает становиться беспорядочным. Метод askFacebookForAuthentication принимает обратный вызов для отказа и успеха, но что происходит, когда FB.login преуспевает, но FB.api терпит неудачу? Этот метод всегда вызывает обратный вызов success независимо от результата метода FB.api.

Теперь представьте, что вы пытаетесь закодировать надежную последовательность из трех или более асинхронных операций таким образом, чтобы надлежащим образом обрабатывать ошибки на каждом шаге и быть разборчивыми для кого-либо еще или даже для вас через несколько недель. Возможно, но очень просто просто вставить эти обратные вызовы и потерять следы ошибок на этом пути.

Теперь отложите API Facebook на мгновение и просто рассмотрите API Angular Promises, реализованный службой $q. Шаблон, реализованный этой службой, представляет собой попытку превратить асинхронное программирование обратно в нечто похожее на линейную серию простых операторов, с возможностью "выбросить" ошибку на любом этапе и обработать ее в конце, семантически подобную знакомый блок try/catch.

Рассмотрим этот надуманный пример. Скажем, у нас есть две функции, где вторая функция потребляет результат первого:

 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.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 также принимает обработчик ошибок в качестве необязательного второго аргумента. Вот еще один, более длинный пример цепочки обещаний, на этот раз с обработкой ошибок:

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

Как вы можете видеть в этом примере, каждый обработчик в цепочке имеет возможность перенаправлять трафик на следующий обработчик ошибок вместо следующего обработчика успеха. В большинстве случаев у вас может быть один обработчик ошибок в конце цепочки, но вы также можете иметь промежуточные обработчики ошибок, которые пытаются восстановить.

Чтобы быстро вернуться к вашим примерам (и вашим вопросам), я просто скажу, что они представляют два разных способа адаптации API обратного вызова Facebook к Angular способу наблюдения за изменениями модели. Первый пример завершает вызов API в обещании, которое можно добавить в область видимости и понимается системой шаблонов Angular. Второй подход использует более грубую силу для задания результата обратного вызова непосредственно в области, а затем вызывает $scope.$digest(), чтобы сделать Angular осведомленным об изменении внешнего источника.

Два примера не сопоставимы напрямую, потому что в первом отсутствует шаг входа. Однако обычно желательно инкапсулировать взаимодействия с внешними API-интерфейсами, как это в отдельных службах, и доставлять результаты контроллерам как promises. Таким образом, вы можете держать свои контроллеры отдельно от внешних проблем и легче тестировать их с помощью макетных сервисов.

Ответ 2

Я ожидал сложный ответ, который будет охватывать оба: почему они используются в общий и как его использовать в Angular

Это plunk для angular promises 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/


Обновление 28 февраля 2014 года: Начиная с версии 1.2.0, promises больше не разрешаются шаблонами. http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html

(пример плунжера использует 1.1.5.)

Ответ 3

Отложенная представляет собой результат асинхронной операции. Он предоставляет интерфейс, который может использоваться для сигнализации состояния и результата операции, которую он представляет. Он также предоставляет способ получения связанного экземпляра обещания.

Обещание обеспечивает интерфейс для взаимодействия со связанными с ним отсрочками, и поэтому позволяет заинтересованным сторонам получить доступ к государству и результат отложенной операции.

При создании отложенного состояния его состояние находится на рассмотрении, и оно не имеет никакого результата. Когда мы разрешаем() или отклоняем() отложенное, оно изменяет свое состояние на разрешенные или отклоненные. Тем не менее, мы можем получить связанное обещание сразу после создания отложенного и даже назначить взаимодействия с его будущим результатом. Эти взаимодействия будут происходить только после отложенного отклонения или разрешения.

Ответ 4

используйте обещание в контроллере и убедитесь, что данные доступны или нет

 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="[email protected]*" 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>