Sumama Waheed Sumama Waheed - 6 months ago 31
Javascript Question

Call a promise function multiple times until condition met from another promise function

I have the following code: http://jsfiddle.net/kyy4ey10/4/

$scope.count = 0;

function getActiveTasks() {
var deferred = $q.defer();
setTimeout(function() {
$scope.count++;
deferred.resolve();
},1000)
return deferred.promise;
}

// i want to put this inside the function, put it doesn't work then
var deferred = $q.defer();

function callPromise() {
getActiveTasks().then(function(){
if($scope.count < 5){
callPromise();
}
else{
deferred.resolve()
}
})
return deferred.promise;
}
callPromise().then(function(){
$scope.count = "done"
});


If I put:

var deferred = $q.defer();


inside the callPromise() function, $scope.count doesn't get resolved to "done".

How can I change how I've wrote these two functions and then call callPromise() so I don't have to put var deferred = $q.defer(); outside?



Edit ----------------------------------------------



Follow up question to the correct answer.



Say in the else block, I have call to another promise like so:

return getActiveTasks().then(function(){
if($scope.count < 5){
callPromise();
}
else{
callToAnotherPromise()
.then(function(){
callToThirdPromise().then(function(){
// im official done
// I want to optimize this else block so all err codes are thrown to the top level and passed to the callee
})
})
}
})


For other functions I think I would this but I don't know if im allowed to while having the outer return (return getActiveTasks :

return getActiveTasks().then(function(){
if($scope.count < 5){
callPromise();
}
else{
callToAnotherPromise()
.then(function(){
return callToThirdPromise()
// im official done
// I want to optimize this else block so all err codes are thrown to the top level and passed to the callee
})
}
})


I want all errors from callToAnotherPromise and callToThirdPromise to be thrown here :

callPromise().then(function(){
$scope.count = "done"
},function(err){
// all errors
});

Answer

When you declare deferred outside and recursively call your function the innermost recursive call will resolve the deferred.promise from the outside. When it's declared inside, only the inner-most promise gets resolved (but it's never returned). Consider this fiddle which kind of illustrates a similar variable scoping issue:

https://jsfiddle.net/sg6odtof/

var counter = 5;

var a = "outside";
function b() {
  //If you uncomment this, the behavior of b is different.
  //var a = "inside";
  if (counter > 0) {
    counter--;
    b();
  }

  return a;
}

alert(b());

I'm guessing you don't like that variable outside the function; therefore I think what you want to do is have the outer recursion return the inner-recursion's promise.

http://jsfiddle.net/pLns9mjw/1/

function callPromise() {  
    return getActiveTasks().then(function(){
        if($scope.count < 5) {
            return callPromise();
        }
    });
}

Also, not sure what you're trying to do for real consider $q.all which takes in an array of promises and resolves when all promises are done. As long as your tasks were known in advance, that's easy if you just want to notify when they're all done.

If the ongoing tasks is dynamic which might get added, you could $q.all them, and check to see if any are left when that promise is done until there are none left.

If you want to just do an ongoing status that reports to the user when you're at certain thresholds a timeout approach like yours is fine.

Note you should use $timeout instead of vanilla JavaScript timeouts so that any changes to the $scope don't happen outside the Angular's digest cycle.

Comments