Chaining Angular Promises

Angular Promises can be a difficult topic to master, and I’ve created a project to help understand how promises work and how services can be built to use them. For our example, we need to make a series of http requests in order to complete our transaction.

Our MainController is responsible for posting a transaction when a user clicks our “Sale” button, update them of the status, and display a response. We want to abstract any of the business logic in making these requests, so we will utilize our reusable TransactionService to submit our transaction, and user our controller to manipulate our UI based on our response.

function MainController(TransactionService, $log) {
    var vm = this;

    vm.runTransaction = runTransaction;
    vm.resetStatus = resetStatus;
    vm.status = '';
    vm.response = '';

    function runTransaction() {
      $log.debug("main.runTransaction started...");
      vm.response = 'YOU MUST GATHER YOUR PARTY BEFORE VENTURING FORTH...';
      var transaction = {
        'card': '4XXXXXXXXXX1111'
      };

      TransactionService.postTransaction(transaction)
        .then(function (response) {
          $log.debug("postTransaction returned: " + response);
          vm.response = response;
          vm.status = 'success';
        }, function (response) {
          $log.debug("Post Transaction request failed: " + response);
          vm.response = response;
          vm.status = 'failure';
        });

      vm.status = 'submitted';
    }

Our TransactionService handles all of our business logic, and only needs to be aware of the transaction it needs to process. It first needs to fetch two responses from different resources, our url, and our token. When both of those promises are resolved, q.all.then() executes, which can call our GatewayService with those required parameters.

function TransactionService(UrlService, TokenService, GatewayService, $q, $log) {
  var that = this;

  that.postTransaction = postTransaction;
  that.gatewayUrl = '';
  that.token = '';
  that.response = '';

  function postTransaction(transaction) {
    $log.debug("TransactionService.postTransaction() called with card: " + transaction.card);
      var independentPromises = [UrlService.getUrl(), TokenService.getToken()];
      return $q.all(independentPromises)
        .then(function(results){
          $log.debug('Prerequisite requests success: ' + JSON.stringify(results));
          that.gatewayUrl = results[0];
          that.token = results[1];
          return GatewayService.postRequest(that.gatewayUrl, that.token, transaction);
        });
  }
}

function UrlService($http, $q, $log) {First, we make a call to our UrlService. It checks to see if we have a cached url, and if it does, resolves the promise and returns it immediately. If not, it has to fetch this from an http resource (simulated with a setTimeout and a get request to a file to simulate a response). It validates the http response for valid data, which gives us the ability to reject this request if we receive a valid response, but an unexpected response body.

function UrlService($http, $q, $log) {
    var that = this;
    that.url = '';
    that.getUrl = getUrl;

    function getUrl() {
      $log.debug("UrlService.getUrl called");
      return $q(function (resolve, reject) {
        if (that.url !== '') {
          $log.debug('Returning cached url: ' + that.url);
          resolve(that.url);
        } else {
          $log.debug('No cached url, making http request...');
          setTimeout(function () { // Artificial delay
            $http.get('app/resource/url.json')
              .then(function (result) {
                if (isValidResponse(result)) {
                  $log.debug("url get received valid response: " + JSON.stringify(result));
                  that.url = parseResponse(result);
                  resolve(that.url);
                } else {
                  $log.debug("Rejecting due to invalid response");
                  reject('Invalid URL Response.');
                }
              })
              .catch(function(result){
                  $log.debug('FAILED Token, result:' + JSON.stringify(result));
                  reject('URL Request Failed.');
              });
          }, 1000);
        }
      });

      function isValidResponse(result) {
        $log.debug('Checking valid response: ' + JSON.stringify(result));
        return true;
      }

      function parseResponse(result) {
        return result.data.gateway;
      }

    }

  }

The implementation of token service is almost identical, so for brevity’s sake, we’ll assume it functions similarly.

Lastly, we make a request to the GatewayService, passing in our url, token, and transaction. This functions like previous http requests, but in a real world scenario the $http.get() would use the aforementioned 3 parameters as part of the request.

This design delegates our UI to the MainController, the business logic to the TransactionService, and the implementation of our resources to our UrlService and TokenService. Promises ensure our dependent service, Gateway Service, has it’s necessary parameters to run.

Thanks to angularjs.reddit.com and /u/gabedamien for your help cleaning this up!

Posted in Examples Tagged with: ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*