EasterBunnyBugSmasher
EasterBunnyBugSmasher

Reputation: 1582

a promise for a recursive data structure

I want to resolve a recursive data structure before calling a controller, but I'm struggling with how to do this.

my current code looks like this:

region.loadChildren = function() {
    region.children = [];
    return region.resource.all('children').getList().then(function(subs) {
        angular.forEach(subs, function(sub) {
            sub.loadChildren();
            region.children.push(sub);
        });
      }, errorCallback
    );
};

can I somehow "collect" the promises returned by sub.loadChildren() and combine them?

Upvotes: 1

Views: 252

Answers (4)

georgeawg
georgeawg

Reputation: 48968

Use $q.all to consolidate promise collections and be sure to return them for chaining.

region.loadChildren = function() {
    //return for chaining
    return region.resource.all('children').getList().then(function(subs) {
        var children = [];
        angular.forEach(subs, function(sub) {
            var p = sub.loadChildren();
            children.push(p);
        });
        //return for chaining
        return $q.all(children);
    );
};

Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1

For more information, see AngularJS $q service API Reference -- chaining promises.

Be aware that $q.all is not resilient. If one of the promises is rejected, $q.all will resolve rejected with the first error.

Upvotes: 1

Ran Sasportas
Ran Sasportas

Reputation: 2266

$q.all is youre solution -

region.loadChildren = function() {
    region.children = [];
    return region.resource.all('children').getList().then(function(subs) {
        var promises = subs.map(subs, function(sub) {
            var promise = sub.loadChildren();
            region.children.push(sub);
            return promise;
        });

        var allPromisesInOnePromise = $q.all(promises); // a regular promise which will resolve when all of the promises will resolve

      }, errorCallback
    );
};

you can read more about promises here and the $q.all function here- https://docs.angularjs.org/api/ng/service/$q

Upvotes: 1

Alnitak
Alnitak

Reputation: 339816

You can replace your current function with one that creates the initial array into which the promises will be accumulated, and then pass that to a modified version of your original function that does the real work:

region.loadChildren = function() {
    var tmp = [];
    this.loadChildrenHelper(tmp);
    this.children = tmp;
}

region.loadChildrenHelper = function(accum) {
    return region.resource.all('children').getList().then(function(subs) {
        angular.forEach(subs, function(sub) {
            sub.loadChildrenHelper(accum);
            accum.push(sub);
        });
      }, errorCallback
    );
};

and then use $q.all on the resulting array

Upvotes: 0

Kristján
Kristján

Reputation: 18803

Yep, you can use $q.all to gather the promises into a single promise that resolves when each promise in the list has resolved.

var promises = []
angular.forEach(subs, function(sub) {
  promises.push(sub.loadChildren());
  region.children.push(sub);
});
$q.all(promises).then(function(values) {
  // Finished
})

Upvotes: 2

Related Questions