1

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, but it doesn't work
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 written these two functions and then call callPromise() so I don't have to put var deferred = $q.defer(); outside?

Sumama Waheed
  • 3,579
  • 3
  • 18
  • 32
  • Create an inner function that you call recursively. Put the defer inside of the outer function, but outside of the inner function. Several examples of this construct [here](http://stackoverflow.com/questions/37078220/how-to-make-an-array-of-deferred-objects/37083320#37083320) - (see the `runMore()` and `next()` functions). – jfriend00 May 07 '16 at 04:15

1 Answers1

2

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.

Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
  • 1
    wow thanks for the awesome explanation with the awesome answer – Sumama Waheed May 07 '16 at 04:15
  • What is the purpose of the `else` clause in the 2nd code example? And, why you are creating a deferred and resolving it there, but not returning anything? – jfriend00 May 07 '16 at 04:16
  • @jfriend00 Good point. In JavaScript a function that exits without a return statement with an expression nothing returns undefined. The Promise API will use what is returned if it is a Promise, or if it isn't a Promise, wrap it in one. In my case I left out the explicit return, but JavaScript was still returning undefined, which the Promise API was wrapping as a Promise. Since the caller wasn't consuming the Promise result, I didn't notice the typo. – Jesus is Lord May 07 '16 at 04:17
  • @WordsLikeJared - It doesn't really wrap it as a promise. The return value of a `.then()` handler becomes the resolved value of the parent promise. The lines of code in the `else` statement are just not doing anything - not sure why they are there at all. – jfriend00 May 07 '16 at 04:19
  • @jfriend00 I kept the defer and resolve because I was trying to preserve OP's code (I assume it was a toy example for something more complicated); I would have just taken out the `else` altogether in this case, since the final `then` didn't need a result value. – Jesus is Lord May 07 '16 at 04:20
  • would it make sense to put a err handler as such: return getActiveTasks().then(function(){res},function(err)) or only the callee would have it? – Sumama Waheed May 07 '16 at 04:23
  • yes its a toy example for something a bit more complicated, but good to know I don't need those 3 lines in the else @jfriend00 – Sumama Waheed May 07 '16 at 04:24
  • @SumamaWaheed How you structure error handling is probably a more general question - what does your team do, etc. If you need to do something for that particular promise (like log it - maybe it's a call to an external something that you want to explicitly do something with), sure; unless I have a compelling reason otherwise, I tend to try to do the error handling at the very "top level". – Jesus is Lord May 07 '16 at 04:25
  • @SumamaWaheed I've been coding Angular for years and I don't think I've ever used `$q.defer`. I normally use `$q.all` to resolve when several promises are done, `$q.when` to make non-promises into a promise base-case - maybe an `if`-`else` where `if` returns a promise (maybe needs to make an `$http` call), but the `else` returns a cached value - that way the containing function always returns a promise. Occasionally I use `$q.reject`. Just like `$scope.apply` is usually for "Angular-izing" things that aren't Angular, `$q.defer` would be "Promise-izing" things that aren't already Promises. – Jesus is Lord May 07 '16 at 04:31
  • @SumamaWaheed It's usually not good for this site to edit the meaning of a question to basically ask a second question. Go ahead and make a new question that demonstrates the specifics of your new question. Smaller focused questions are better for future readers. That being said, without knowing the specifics, make sure your `callPromise` function always returns a promise through every path. Inside the `if`, return a promise. Inside the `else`, return a promise. It should just work, then. In general, usually if one path of a function returns a promise, the others should, too. – Jesus is Lord May 07 '16 at 04:52
  • @SumamaWaheed [Here's an example of `$q.all` that does something like yours.](http://jsfiddle.net/ga9ntjwu/1/). – Jesus is Lord May 07 '16 at 05:09
  • @WordsLikeJared thanks I got it working as you suggested and deleted the edit – Sumama Waheed May 07 '16 at 05:28