Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async functions inside forEach loops with js [duplicate]

Tags:

javascript

Possible Duplicate:
javascript: execute a bunch of asynchronous method with one callback

I've been struggling with this issue for days now, but I just can't figure out an elegant way to handle it. Here's the problem.

I'm running a forEach loop, and I need to know when everything has been completed. Since forEach loops are blocking, it should be easy enough to just drop a console.log after the forEach loop, which will run when the forEach loop is complete. However, if there is any function inside of the forEach loop that's not sync, this entire system breaks down very quickly, and I have no way of knowing when the contents of the loop are complete other than very strange hacky counters and if statements. My question is whether there is an elegant way to handle this situation in javascript.

Here are a couple examples:

In this case, the forEach loop works perfectly nicely, since everything in the loop is sync.

[1,2,3].forEach(function(num){
  console.log("we're on " + num);
});
console.log('done counting!')

//=> we're on 1
//=> we're on 2
//=> we're on 3
//=> done counting!

This is where we start running into problems though, as a basic example:

[1,2,3].forEach(function(num){
  setTimeout(function(){ console.log(num) }, 200);
});
console.log('done counting!');

//=> done counting!
//=> we're on 1
//=> we're on 2
//=> we're on 3

Although it does make sense that this is happening, I now have the unfortunate issue on my hands that I need a callback for when we are done counting, and there's no smooth way for me to have one other than something gross like this:

var counter = 0;
var numbers = [1,2,3]

var log_number = function(num){
  console.log("we're on " + num);
  counter++;
  if (counter == numbers.length) {
    console.log("done counting!")
  }
}

numbers.forEach(function(num){
  setTimeout(log_number(num), 200);
});

//=> we're on 1
//=> we're on 2
//=> we're on 3
//=> done counting!

Goodness, that's an awful lot of overhead. Is there any smoother way to make this happen? I've checked out promises, deferred's, and sequences, but I haven't really been able to implement any one of them in a way that makes this more concise : (

To be clear, it's not setTimeout specifically that is my issue, it generally having async functions running inside a sync function - that was just the simplest example.

like image 508
Jeff Escalante Avatar asked Nov 04 '12 00:11

Jeff Escalante


2 Answers

Array.prototype.forEachDone = function(fn, scope, lastfn) {
    for(var i = 0, c = 0, len = this.length; i < len; i++) {
        fn.call(scope, this[i], i, this, function() {
            ++c === len && lastfn();
        });
    }
};

[1,2,3].forEachDone(function(num, i, arr, done){
  setTimeout(function(){ console.log(num); done() }, 200);
}, this, function() {
    console.log('done counting!');
});

this will do what you're looking for. forEachDone will take the exact same parameters as forEach, with an additional one at the end that is a callback to be called when the functions applied to each element of the array are done. the function that'll be applied to each element in the array also takes the exact same parameters as in forEach but also with an additional one that is a function that should be called when the function finishes.

PS: personally, i'd never touch a native object's prototype, but if you're looking for pretty, this is it.

like image 159
zertosh Avatar answered Sep 22 '22 02:09

zertosh


var list = ["your", "mom"];
var count = 0;

for(var i = 0; i < list.length; ++i) {
    getSomething(list[i], function() {
     if(++count == list.length)
      imDone();
     });
}

another way is like this

var biscuits = ["cheddar", "butter milk"];

(function eatBiscuits(location) {
  eatIt(biscuits[++location], whenAllEaten);
  typeof biscuits[location] != "undefined" && eatBiscuits(location);
}(-1))

function eatIt(biscuit, cb) {
 if (typeof biscuit == "undefined") {
  cb();
 } else {
  stuffMyFace();
 }
}
like image 31
samccone Avatar answered Sep 22 '22 02:09

samccone