2

In the trivial example below, I have a method call (getMoreProducts) that needs to be called after the first method call (getProducts) has completed.

As I am new to angularjs (and fairly new to JS), I would like to confirm if there is another way of achieving this. My concern is that if another method needs to be called after getMoreProducts, the code will then be three levels deep, and so on.

.controller('ProductController',function(ProductService, $scope){
  $scope.products = [];
  $scope.moreProducts  = [];

  ProductService.getProducts().then(function(res){
     $scope.products = res.data;

     ProductService.getMoreProducts().then(function(res){
        $scope.moreProducts = res.data;
     });
  });

For this example, assume the ProductService methods are simply calling invoking a HTTP GET call.

John Steed
  • 599
  • 1
  • 12
  • 31

2 Answers2

1

The antipattern that is used in nested promises in the OP is a form of 'callback hell', this is exactly what promises are supposed to help against.

When next call depends on the previous call, promises should be chained in series, this way they are no more than 1 level deep:

  ProductService.getProducts().then(function (productsRes) {
     $scope.products = productsRes.data;
     return ProductService.getMoreProducts();
  })
  .then(function (moreProductsRes) {
      $scope.moreProducts = moreProductsRes.data;
     return ProductService.getEvenMoreProducts();
  })
  .then(function (evenMoreProductsRes) { ... });

When promises don't depend on the results of each other (like in this case), they can be executed in parallel, and this is what $q.all is for:

$q.all([ProductService.getProducts(), ProductService.getMoreProducts()])
.then(function (responses) {
   var productsRes = responses[0];
   var moreProductsRes = responses[1];
   $scope.products = productsRes.data;
   $scope.moreProducts = moreProductsRes.data;
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
0

Yes, you can leverage promise chaining. If you return a promise within another, you can avoid nasty nesting:

var app = angular.module('plunker', []);


// Before
app.controller('MainCtrl', function($scope, ProductService) {
  $scope.name = 'World';

  $scope.products = [];
  $scope.moreProducts  = [];

  ProductService.getProducts().then(function(res){
    $scope.products = res.data;

    ProductService.getMoreProducts().then(function(res){
        $scope.moreProducts = res.data;
    });
  });
});

// After with promise chaining
app.controller('MainCtrl', function($scope, ProductService) {
  $scope.name = 'World';

  $scope.products = [];
  $scope.moreProducts  = [];

  ProductService.getProducts().then(function(res){
    $scope.products = res.data;
    return ProductService.getMoreProducts()
  })
  .then(function(res){
      $scope.moreProducts = res.data;
  });
});


app.service('ProductService', function($timeout){
  this.getProducts = function() {
    return $timeout(function(){
      return {
        data: [{name: 'Widget', color: 'blue'}]
      }
    }, 500)
  }

  this.getMoreProducts = function() {
    return $timeout(function(){
      return {
        data: [{name: 'Widget', color: 'green'}, {name: 'Widget', color: 'red'}]
      }
    }, 1000)
  }
})

Depending on the requirements for the getProducts and getMoreProducts calls, you can use arguments instead of making different functions.

http://plnkr.co/edit/K4LPKWSD06QnwbRk8k0B?p=preview

Edit:

By the way, I don't recommmend this style of creating controllers and services, it's just the default plunker template.

See Y021 Y022 Y031

Phix
  • 9,364
  • 4
  • 35
  • 62