Angular.js: Return promise from angular.forEach() to allow action when all done

Created on 7 Mar 2014  路  5Comments  路  Source: angular/angular.js

(this is my first attempt at an AngularJS suggestion, so if I'm getting the process wrong then let me know)

It would be good if the angular.forEach() function returned a promise, so that we can write e.g.

      $scope.users.$promise.then(function(users) {
        angular.forEach(users, function(user) {
          if(user.type === "test") {
            user.$delete(function success() {
              console.log("Deleted a test user");
            }, function failure() {
              console.log("Failed to delete a test user!");
            });
          }
        }).then(function() {
          console.log("Deleted all test users");
        });
      });

Most helpful comment

I am not going to comment on the process for leaving suggestions, but I am going to say a couple things about this:

1) angular.forEach is actually a completely synchronous operation, so returning a promise doesn't make a lot of sense here.

2) angular.forEach is actually primarily used as a polyfill for es5 forEach within the framework itself, and it has been stated that it was probably a mistake to export it. Making this change could cause performance hits internal to the framework and generally be pretty nasty

However, those points don't mean that something like this couldn't be useful in your own applications, so I might suggest implementing a helper for it:

angular.module("qEach", [])
.factory("qEach", ["$q", function(q) {
  function qEach(collection, promiseFactory) {
    // ASSERT typeof collection === "object"
    // ASSERT typeof promiseFactory === "function"
    var deferred = q.defer();
    var promises = _.map(collection, function(value) {
      var val = promiseFactory(value);
      if (typeof val === "undefined") val = value;
      return q.when(val);
    });
    q.all(promises).then(function(values) {
      deferred.resolve(values);
    }, function(reason) {
      deferred.reject(reason);
    });
    return deferred.promise;
  }
  return qEach;
}]);

(This is entirely untested, but something like that should do what you want)

All 5 comments

I am not going to comment on the process for leaving suggestions, but I am going to say a couple things about this:

1) angular.forEach is actually a completely synchronous operation, so returning a promise doesn't make a lot of sense here.

2) angular.forEach is actually primarily used as a polyfill for es5 forEach within the framework itself, and it has been stated that it was probably a mistake to export it. Making this change could cause performance hits internal to the framework and generally be pretty nasty

However, those points don't mean that something like this couldn't be useful in your own applications, so I might suggest implementing a helper for it:

angular.module("qEach", [])
.factory("qEach", ["$q", function(q) {
  function qEach(collection, promiseFactory) {
    // ASSERT typeof collection === "object"
    // ASSERT typeof promiseFactory === "function"
    var deferred = q.defer();
    var promises = _.map(collection, function(value) {
      var val = promiseFactory(value);
      if (typeof val === "undefined") val = value;
      return q.when(val);
    });
    q.all(promises).then(function(values) {
      deferred.resolve(values);
    }, function(reason) {
      deferred.reject(reason);
    });
    return deferred.promise;
  }
  return qEach;
}]);

(This is entirely untested, but something like that should do what you want)

Thanks for the quick feedback Caitlin!

I only ended up using angular.forEach() in the code above because I couldn't for the life of me find another way to iterate through the user in users. Do you know of any? (maybe that's what your code above does, but I can't tell. looked for a simple "for (user in users)" kind of construct, even if I have to call then() on that user too)

If you have an Array, for (index = 0, length = array.length; index < length; ++index) item = array[index] --- if you otherwise have an Object, for (key in object.getOwnPropertyNames()) item = object[key] (or similar, depending on browser support requirements).

You can use a helper like lodash forEach or forOwn, or other helpers.

Gee, I thought the full for-syntax and the "for (x in xes)" was equivalent, but it seems not (my Javascript is too rusty). The former returns the Resource objects each representing a User that I needed, while printing the latter just show an incrementing count (not sure what than implies).

Guess this might be the most trivial implementation of what I was looking for initially:

  $scope.deleteTestUsers = function() {      
    $scope.users.$promise.then(function(users) {

      var ops = [];
      for(var i = 0; i < users.length; i++) {
        ops.push(
          users[i].$delete(function success() {
            console.log("Deleted a test user");
          }, function failure() {
            console.log("Failed to delete a test user!");
          })
        );
      }

      $q.all(ops).then(function() {
        console.log("All done!");
      });
    });
  }

Any way I can do without the ops array?

I will sometimes return promises in place of a break of a break.

this.retrieveTail = function (id) {
    var defer = $q.defer();
    angular.forEach(items, function (item) {
        if (item.id === id) {
            defer.resolve(item);
        }
    });
    return defer.promise;
};
Was this page helpful?
0 / 5 - 0 ratings