4

I have a async function, which can recurse into itself.

I'm adding jQuery deferreds to an array each time the function runs, an use $.when() to check if all promises have resolved. My issue is that I can't seem to do this without an arbitary timeout, as I simply don't know when the function stops recursing and adding new promises.

silverwind
  • 3,296
  • 29
  • 31
  • Maybe I'm overthinking this, and it can be solved with setTimeout/clearTimeout, but I'm still interested in other approaches. – silverwind Apr 07 '14 at 09:43
  • Does the function itself know when it's done? You could add a `recursionComplete` deferred to the array at the start and only resolve that when you're done. – david Apr 07 '14 at 09:43
  • No, the function doesn't know when it's done. It reads a filesystem tree and can always encounter new directories/files. – silverwind Apr 07 '14 at 09:47
  • Do you know when you add your last `promise` to `promises`? If so can't you just *start checking* from that point, [like this](http://jsbin.com/fafaniho/1/edit)? – Ian Clark Apr 07 '14 at 09:53
  • did you have thought to use a callback AFTER the tasks ? – KarelG Apr 07 '14 at 09:55
  • @IanClark: That could work if the FileReader API always does directories fist, I'll have to try. – silverwind Apr 07 '14 at 10:21
  • @KarelG: The main problem is, that I don't know exactly when the task finishes. I have a solution in mind which sets and resets a timeout, and when that timeout finishes, I know that the function wasn't called in the last x milliseconds. This wouldn't even need promises, but still isn't optimal in case the function takes longer than the timeout to complete (like when it hits a big file). – silverwind Apr 07 '14 at 11:29
  • the callback method gets invoked after the task is done. See for examples at AJAX requests ... – KarelG Apr 07 '14 at 11:32

2 Answers2

3

Do push to promises only synchronously, so that you know when you are finished. Since your function is recursive and returns a promise, you can just use the result promises of the recursive calls for this. Use $.when() only for the results of the current recursion level, not for all promises in the call stack.

Also check my answers to Understanding promises in node.js for recursive function and Is there an example of using raw Q promise library with node to recursively traverse a directory asynchronously? - even though they're not using jQuery's implementation, they should give you an idea of how to do it.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    +1 This sounds like the the correct idea. Worth mentioning promises already recursively unwrap by design so it's possible to simply do `this.then(function(){ /* actual calc creating kids */ return $.when.apply(children); })` – Benjamin Gruenbaum Apr 07 '14 at 12:00
  • 1
    Yeah, though the "recursively" is not even needed here, just the "unwrap" is important :-) – Bergi Apr 07 '14 at 12:02
  • [It's done](https://github.com/silverwind/droppy/blob/master/src/client.js#L1567). Thinking in seperate levels for each recursion was the key that helped me understand this! – silverwind Apr 15 '14 at 13:05
2

Just to add to what Bergi said, and to clarify the underlying issue:

There is no way to know when no more promises will be chained to a promise with .then. Just like there is no way to know from a function where it returns:

myPromise().then(function(vale){
        // I have no easy way to know when handlers will no longer be added to myPromise
        // from myPromise itself
});

Is just like:

 function mySynchronousFunction(){
      // I can't easily tell from here where this code returns or how many time it will
      // be called in the entire program lifecycle.
 };

How would you solve that problem with synchronous code? You probably wouldn't have it in the first place. You'd know better than to rely on global program state and the caller inside a function :)

It's similar to promises. You don't rely on when the last handler was added in promise code.

Instead, what you want to do is call $.when on the inner promises inside the .forEach in your practical example which would give you a hook on when they're all done, and only resolve them in turn when their inner promises resolve - which is just returning them really since promises unwrap recursively.

In addition to Bergi's samples which are all good - see Petka's implementation which should give you the general idea :)

Good luck.


As a side note - you're not really using promises as promise at all. You're more using them as callbacks. If you want a promise to wait for another promise to complete, you can use .then:

promiseReturning1().then(promiseReturning2).then(promiseReturning3).then(function(lastResult){
       // all done here
});

Instead of implementing your own asynchronous queue.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Another analogy: A changing promise reference is like a boxed variable (where a single promise is a boxed _value_). In synchronous code - you have a variable and multiple places in the program access it - there is no obvious way to know when no one in the program will change the variable's value anymore - that's your responsibility to coordinate that . The fact it's hard to enforce with globals in synchronous code is practically the problem with global variables. – Benjamin Gruenbaum Apr 07 '14 at 12:11
  • Thanks, I'll have a look at these answers later. I only began experimenting with Promises yesterday, so I still have much to learn :) – silverwind Apr 07 '14 at 15:00
  • @silverwind take your time and welcome to the better future :) – Benjamin Gruenbaum Apr 07 '14 at 16:40