4

I hope this will make sense: I need to create a foreach function in javascript that will be used like this:

foreach(["A", "B", "C"], function(letter, done) {
    // do something async with 'letter'
    doSomthing(letter, done); // ***
}, function () {
    // final callback that is called after all array has been visted.
    // do some final work
});

So I was thinking about the following implementation:

var foreach = function(array, func, ready) {
    if (!array.length)
        ready();
    var that = this;
    func(array[0], function(){
        that.foreach(array.slice(1, array.length), func, ready);
    });
}

And it seems that it actually works! very cool.

But I was thinking if there is a solution that doesn't use recursion? I couldn't think of one...

Ofri
  • 474
  • 5
  • 13
  • does the first function (second parameter to `foreach`) need to operate on each element in the array asynchronously? – Matt Dodge Jun 13 '12 at 04:48
  • 2
    There's nothing asynchronous about this code. What are you trying to convey? – Aadit M Shah Jun 13 '12 at 04:49
  • 1
    Theres something asynchronous about the title of the question... – Matt Dodge Jun 13 '12 at 04:50
  • yes exactly! this callback is asynchronous. in my use case it's a function that "require"s the files named in the array. – Ofri Jun 13 '12 at 04:52
  • what do you mean? maybe I should have said "non-blocking"? – Ofri Jun 13 '12 at 04:53
  • If you guys think I'm missing something - please tell me. I was just thinking about the best way to implement a foreach function that takes a non-blocking callback – Ofri Jun 13 '12 at 04:55
  • I think I see the problem. let me edit the code a bit - I can't directly call done()... – Ofri Jun 13 '12 at 04:57
  • The problem with your code is that `foreach` is not asynchronous. It calls the function `func` synchronously and thus only returns after the `func` function returns. The `func` function in turn calls the `done` function synchronously which in turn calls the `foreach` function again and again until all the elements of the array are processed at which point it calls the `ready` function synchronously. Thus your code recursively calls `foreach` and blocks other code after the `foreach` function from being processed until the `ready` function returns. Hope that clears things in your mind. – Aadit M Shah Jun 13 '12 at 06:06
  • You're right, this is the solution I came up with to what I needed, which is to have a callback fire once all the array elements have been "visted with a non-blocking function". Obviusly this is not the best solution because, as you said, the next element waits for the last one to complete... – Ofri Jun 14 '12 at 04:20
  • You should probably accept one of the answers on your question if you want people to answer your questions in the future. There's a tick mark beside each answer. Just click on the tick mark beside the answer which you think is acceptably addresses your question. – Aadit M Shah Jun 15 '12 at 06:14
  • sorry I was not aware of that – Ofri Jun 17 '12 at 07:21

6 Answers6

1

Your approach is technically correct but it is not good to do in such a way. Pls implement using promise pattern in javasript . I recommend you using when.js an open source js available on git for implementing promise pattern Pls refert to the below code

   var service = {
            fetch: function (query) {

                // return a promise from the function
                return when(["A", "B", "C"].forEach(function (name) {
                    alert(name);
                }));
            }
        };

        service.fetch("hello world").then(function () {
            alert("work has been completed");
        });
Ajay Beniwal
  • 18,857
  • 9
  • 81
  • 99
  • Thanks! this is the kind of answer I was hoping for. What do you mean by "not good to do" is it because of preformance? – Ofri Jun 13 '12 at 05:05
  • performance wise i dont see any issue. I have modified the code with a example which can help – Ajay Beniwal Jun 13 '12 at 05:48
  • This should be used with async operations generally but the syntax remains the same – Ajay Beniwal Jun 13 '12 at 05:56
  • Note that `when` just resolves when all its input promises have resolved. They have already been launched as promises, so they run in parallel. A true `forEach` is different: it calls a function to create each promise, one at a time, only creating the next one when the previous has resolved. The first fail causes the whole thing to fail. – Daniel Earwicker Jan 15 '14 at 15:38
  • This answer makes it seem like the only correct way to manage Async functions in JavaScript is to use promises. While promises have their benefits, they also have shortcomings and detractors, and there are many other useful patterns in JavaScript that are equally as beneficial and elegant to solve the problem. While the answer is technically correct, it relies on a pattern that is not universal. – mjsalinger Apr 28 '14 at 18:40
1

I just am here again for academic purposes at this point. Now, I recommend everyone to understand Aadit's approach in its elegance and terseness first because it was a nice learning experience for me in his technique of using bind as I did not know about that nor did I know about the additional arguments you can place after setTimeout.

After learning these things, I've reduced my code down to this:

    var foreach = function(array,doSomething,onComplete) {
        var i = 0, len = array.length, completeCount = 0;
        for(;i < len; i++) {
            window.setTimeout(function() {
                doSomething(arguments[0]);
                completeCount++;
                if (completeCount === len) { 
                    onComplete(); 
                }
            },0,array[i]);
        }
    };

I argue that you must have a "completeCount" because although Aadit's code is a great terse working solution that is reducing the array automatically, it is not truly asynchronous as "next()" is called after each method completes in the array linearly. The "completeCount" allows code to finish execution in any order which is the point of this I believe. In Aadit's code, there is also the side effect of modifying your input array as well as needing to alter the Function prototype in which I argue this is not necessary. "Hoisting" is also not practiced in his code which I think should be done as that style reduces errors.

Again, I respect Aadit's code very much and have taken the time to come back again to try to present a better solution based on what I have learned through other smart folks as well as Aadit. I welcome any critiques and corrections as I will try to learn from it.

FYI: here is one that is a general deferred method

    var deferred = function(methods,onComplete) {
        var i = 0, len = methods.length, completeCount = 0,
        partialComplete = function() {
            completeCount++;
            if (completeCount === len) {
                onComplete();
            }
        };
        for(;i < len; i++) {
            window.setTimeout(function() {
                arguments[0](partialComplete);
            },0,methods[i]);
        }
    };

    // how to call it
    deferred([
        function (complete) {
            // this could easily be ajax that calls "complete" when ready
            complete();
        },
        function (complete) {
            complete();    
        }
    ], function() {
       alert('done'); 
    });
King Friday
  • 25,132
  • 12
  • 90
  • 84
  • 1
    I would like to point out a few misconceptions. First, calling `next` function to process the next element of the array doesn't make the `forEach` function synchronous. This is because the `forEach` method returns before the `funct` function is called as it calls `funct` asynchronously. Calling `next` processes the next element of the array asynchronously too. Hence my implementation of `forEach` is indeed truly asynchronous. – Aadit M Shah Jul 11 '12 at 05:59
  • 1
    Second, maintaining a `completeCount` does not mean that the elements of the array will be processed out of order. When you call `setTimeout` the JavaScript engine puts the code to be executed in a queue. Hence although you are calling each function asynchronously they will always be processed in order. You don't need to take my word for it. See the [output](http://jsfiddle.net/tzpE9/) for yourself. The advantage of using a `next` function is that you may stop the processing of the array at any time you wish by simply not calling `next`. Using a counter is just additional overhead. – Aadit M Shah Jul 11 '12 at 06:06
  • 1
    Third, my implementation of `forEach` does not have any side effects. The input array is never modified. Calling the `slice` method on an array doesn't mutate the array. It simply returns a new array. You can verify that for yourself by examining the following [script](http://jsfiddle.net/dGLvD/1/). Note that when the `done` function is called, the original array is still intact. – Aadit M Shah Jul 11 '12 at 06:11
  • 1
    Fourth, "hoisting" of local variables is automatically done by the interpreter and need not be explicitly done by the programmer. In the end it's just a matter of choice. Personally I feel that hoisting variables is uglier and more error prone. This is because if I copy a small part of a program and do not copy the variable declarations in the beginning of the function it may lead to accidental leaks into the global scope. Read this [answer](http://stackoverflow.com/a/3685090/783743 "performance - JavaScript variables declare outside or inside loop? - Stack Overflow") for more details. – Aadit M Shah Jul 11 '12 at 06:16
  • @Aadit Point 1, you are correct, your code is asynchronous and I was not meaning it wasn't, only that each item is serially completed. This is not bad, in fact it could be the intended behavior. I just wanted to create something which could spawn all the processes at once. Point 2, the counter is needed because jobs may finish in any order as certain tasks can take longer than others. Point 3 is my mistake. Point 4 is more explicit what is happening which is why its preferred in some circles. I'm open to your critique and this is a great learning experience. – King Friday Jul 11 '12 at 17:02
  • 1
    I beg to differ. JavaScript execution happens on a single thread. Thus no matter how long each task takes it will always execute in the same order `setTimeout` was called if the delay is the same. Functions cannot execute in parallel and hence asynchronous functions cannot execute out of order. Thus using a counter is redundant. Read the following [answer](http://stackoverflow.com/a/2318824/783743 "ajax - How to activate two JavaScript functions in parallel? - Stack Overflow") to know more. – Aadit M Shah Jul 11 '12 at 17:16
  • This is precisely why I posted this code. Thank you for your critique. Good stuff to know Aadit . – King Friday Jul 11 '12 at 20:57
  • Here is another resource from John Resig relating to timers and interval I just found. Thought it might be of interest for clarity for others. http://ejohn.org/blog/how-javascript-timers-work/ – King Friday Jul 11 '12 at 21:05
  • @AaditMShah I'm understanding that https://developer.mozilla.org/en/Using_web_workers web workers are executing asynchronously in a sandbox while setInterval and setTimeout are always synchronously executed although are queued up in an asynchronous manner. Wow, I learned a lot here. – King Friday Jul 11 '12 at 21:12
0

Check out the async library. It has several different functions like that, and it's actively supported.

Dean Rather
  • 31,756
  • 15
  • 66
  • 72
  • Thanks! Though I'm still interested in the implementation... I guess I could look at the library code :) – Ofri Jun 13 '12 at 04:59
0

Correct me if I am wrong but from what I understand from your question I believe you want to take an array, serially call a function asynchronously on every member of that array, and then execute a callback function when every member of the array has been processed.

Now to execute a function asynchronously in a browser environment we would do something like this:

Function.prototype.async = function () {
    setTimeout.bind(window, this, 0).apply(window, arguments);
};

alert.async(5);
alert(6);

In the above example the setTimeout function is used to call the given function asynchronously due to which we first see the value 6 and then the value 5 being alerted.

Next, to make your foreach function asynchronous we would do something as follows:

function forEach(array, funct, callback) {
    if (array.length)
    funct.async(array[0], forEach.bind(null, array.slice(1), funct, callback));
    else callback.async();
}

The above solution doesn't use recusrion. Sure the forEach function is referenced within itself but it's only being called in the funct function which is called asynchronously. Hence the forEach function returns before it's called again in the funct function.

I have included links to JS fiddles before each snippet of code. If you have any more doubts I would be happy to answer them.

Edit:

If you do not like modifying the prototype of Function (@kitgui.com) then you may use this modified code:

var async = Function.prototype.call.bind(function () {
    setTimeout.bind(null, this, 0).apply(null, arguments);
});

async(alert, 5);
alert(6);

Since I am not referencing window in the above code it will also work in non-browser environments.

Then we may rewrite the forEach function as follows:

function forEach(array, funct, callback) {
    if (array.length)
    async(funct, array[0], forEach.bind(null, array.slice(1), funct, callback));
    else async(callback);
}

There we have it. No need to modify the prototype of Function. The function body of async is pretty much the same. We simple created an unbound wrapper for it using call.bind. You may see the live demo for yourself.

Bonus:

You can create errbacks using the above pattern as follows (see the live demo):

function forEach(array, funct, callback, error) {
    if (array.length && !error)
    async(funct, array[0], forEach.bind(null, array.slice(1), funct, callback));
    else async(callback, error || null);
}

This is equivalent to the forEachSeries function in caolan's async library in under 10 lines of code.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    actually, your code is so good that mine looks pretty bad, even though its really standard fair and gets the job done. -1 was a bit harsh. I just looked into your stuff and man, that is some cool concepts you have. its doing so much in such a small space. I looked up some of the things you did and very very impressive. super elegant. doing bind() and reducing the array, very cool. that is John Resig good, man. but you are still are a bit of a wiener in the attitude but very awesome code for sure. – King Friday Jun 15 '12 at 21:41
  • Thank you. I appreciate the fact that you took some time to look into my code and understand what I did. I do agree that I was too critical when it came to judging your answer and for that I do apologize. Nevertheless thank you for pointing out my flaws. I'll work upon them. Cheers. =) – Aadit M Shah Jun 16 '12 at 06:24
  • thanks for the update. You have shown me some very good ideas. I wrote a little bit of code to demonstrate my particular need and how I implemented this. http://www.emeraldcode.com/sleep-test.htm It demonstrates AJAX being returned in different times which is really the purpose of me using the counter. If you have suggestions in this scenario which is really what I need, that would be very helpful. – King Friday Jul 14 '12 at 21:40
0

Assuming you want to do raw computation, and you want it asynchronous so it does not block the browser.

I have been using the "setTimeout(Func,0);" trick for about year. Here is some recent research i wrote up to explain how to speed it up a bit. If you just want the answer, skip to Step 4. Step 1 2 and 3 explain the reasoning and mechanics;

// In Depth Analysis of the setTimeout(Func,0) trick.

//////// setTimeout(Func,0) Step 1 ////////////
// setTimeout and setInterval impose a minimum 
// time limit of about 2 to 10 milliseconds.

  console.log("start");
  var workCounter=0;
  var WorkHard = function()
  {
    if(workCounter>=2000) {console.log("done"); return;}
    workCounter++;
    setTimeout(WorkHard,0);
  };

// this take about 9 seconds
// that works out to be about 4.5ms per iteration
// Now there is a subtle rule here that you can tweak
// This minimum is counted from the time the setTimeout was executed.
// THEREFORE:

  console.log("start");
  var workCounter=0;
  var WorkHard = function()
  {
    if(workCounter>=2000) {console.log("done"); return;}
    setTimeout(WorkHard,0);
    workCounter++;
  };

// This code is slightly faster because we register the setTimeout
// a line of code earlier. Actually, the speed difference is immesurable 
// in this case, but the concept is true. Step 2 shows a measurable example.
///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 2 ////////////
// Here is a measurable example of the concept covered in Step 1.

  var StartWork = function()
  {
    console.log("start");
    var startTime = new Date();
    var workCounter=0;
    var sum=0;
    var WorkHard = function()
    {
      if(workCounter>=2000) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: sum=" + sum + " time=" + ms + "ms"); 
        return;
      }
      for(var i=0; i<1500000; i++) {sum++;}
      workCounter++;
      setTimeout(WorkHard,0);
    };
    WorkHard();
  };

// This adds some difficulty to the work instead of just incrementing a number
// This prints "done: sum=3000000000 time=18809ms".
// So it took 18.8 seconds.

  var StartWork = function()
  {
    console.log("start");
    var startTime = new Date();
    var workCounter=0;
    var sum=0;
    var WorkHard = function()
    {
      if(workCounter>=2000) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: sum=" + sum + " time=" + ms + "ms"); 
        return;
      }
      setTimeout(WorkHard,0);
      for(var i=0; i<1500000; i++) {sum++;}
      workCounter++;
    };
    WorkHard();
  };

// Now, as we planned, we move the setTimeout to before the difficult part
// This prints: "done: sum=3000000000 time=12680ms"
// So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms
// We have effectively shaved off 3.1ms of the original 4.5ms of dead time.
// Assuming some of that time may be attributed to function calls and variable 
// instantiations, we have eliminated the wait time imposed by setTimeout.

// LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high 
// performance in mind, make sure your function takes more than 4.5ms, and set 
// the next timeout at the start of your function, instead of the end.
///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 3 ////////////
// The results of Step 2 are very educational, but it doesn't really tell us how to apply the
// concept to the real world.  Step 2 says "make sure your function takes more than 4.5ms".
// No one makes functions that take 4.5ms. Functions either take a few microseconds, 
// or several seconds, or several minutes. This magic 4.5ms is unattainable.

// To solve the problem, we introduce the concept of "Burn Time".
// Lets assume that you can break up your difficult function into pieces that take 
// a few milliseconds or less to complete. Then the concept of Burn Time says, 
// "crunch several of the individual pieces until we reach 4.5ms, then exit"

// Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality
// we could have easilly incremented workCounter 2000 times in under a millisecond.
// So, duh, that should not be made asyncronous, its horrible. But what if you don't know
// how many times you need to increment the number, maybe you need to run the loop 20 times,
// maybe you need to run the loop 2 billion times.

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  for(var i=0; i<2000000000; i++) // 2 billion
  {
    workCounter++;
  }
  var ms = (new Date()).getTime() - startTime.getTime();
  console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 

// prints: "done: workCounter=2000000000 time=7214ms"
// So it took 7.2 seconds. Can we break this up into smaller pieces? Yes.
// I know, this is a retarded example, bear with me.

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var each = function()
  {
    workCounter++;
  };
  for(var i=0; i<20000000; i++) // 20 million
  {
    each();
  }
  var ms = (new Date()).getTime() - startTime.getTime();
  console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 

// The easiest way is to break it up into 2 billion smaller pieces, each of which take 
// only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion
// to 20 million (100x less).  Just adding a function call increases the complexity of the loop
// 100 fold. Good lesson for some other topic.
// prints: "done: workCounter=20000000 time=7648ms"
// So it took 7.6 seconds, thats a good starting point.
// Now, lets sprinkle in the async part with the burn concept

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var index=0;
  var end = 20000000;
  var each = function()
  {
    workCounter++;
  };
  var Work = function()
  {
    var burnTimeout = new Date();
    burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
    while((new Date()) < burnTimeout)
    {
      if(index>=end) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
        return;
      }
      each();
      index++;
    }
    setTimeout(Work,0);
  };

// prints "done: workCounter=20000000 time=107119ms"
// Sweet Jesus, I increased my 7.6 second function to 107.1 seconds.
// But it does prevent the browser from locking up, So i guess thats a plus.
// Again, the actual objective here is just to increment workCounter, so the overhead of all
// the async garbage is huge in comparison. 
// Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part. 

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var index=0;
  var end = 20000000;
  var each = function()
  {
    workCounter++;
  };
  var Work = function()
  {
    if(index>=end) {return;}
    setTimeout(Work,0);
    var burnTimeout = new Date();
    burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
    while((new Date()) < burnTimeout)
    {
      if(index>=end) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
        return;
      }
      each();
      index++;
    }
  };

// This means we also have to check index right away because the last iteration will have nothing to do
// prints "done: workCounter=20000000 time=52892ms"  
// So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds.
// The Burn Time is the number you tweak to get a nice balance between native loop speed
// and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster
// than 50ms gui response.

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var index=0;
  var end = 20000000;
  var each = function()
  {
    workCounter++;
  };
  var Work = function()
  {
    if(index>=end) {return;}
    setTimeout(Work,0);
    var burnTimeout = new Date();
    burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future
    while((new Date()) < burnTimeout)
    {
      if(index>=end) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
        return;
      }
      each();
      index++;
    }
  };

// prints "done: workCounter=20000000 time=52272ms"
// So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout
// have been eliminated as long as the burn time is anything over 4.5ms
///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 4 ////////////
// The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it.
// Here is a short library that embodies these concepts and gives a descent interface.

  var WilkesAsyncBurn = function()
  {
    var Now = function() {return (new Date());};
    var CreateFutureDate = function(milliseconds)
    {
      var t = Now();
      t.setTime(t.getTime() + milliseconds);
      return t;
    };
    var For = function(start, end, eachCallback, finalCallback, msBurnTime)
    {
      var i = start;
      var Each = function()
      {
        if(i==-1) {return;} //always does one last each with nothing to do
        setTimeout(Each,0);
        var burnTimeout = CreateFutureDate(msBurnTime);
        while(Now() < burnTimeout)
        {
          if(i>=end) {i=-1; finalCallback(); return;}
          eachCallback(i);
          i++;
        }
      };
      Each();
    };
    var ForEach = function(array, eachCallback, finalCallback, msBurnTime)
    {
      var i = 0;
      var len = array.length;
      var Each = function()
      {
        if(i==-1) {return;}
        setTimeout(Each,0);
        var burnTimeout = CreateFutureDate(msBurnTime);
        while(Now() < burnTimeout)
        {
          if(i>=len) {i=-1; finalCallback(array); return;}
          eachCallback(i, array[i]);
          i++;
        }
      };
      Each();
    };

    var pub = {};
    pub.For = For;          //eachCallback(index); finalCallback();
    pub.ForEach = ForEach;  //eachCallback(index,value); finalCallback(array);
    WilkesAsyncBurn = pub;
  };

///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 5 ////////////
// Here is an examples of how to use the library from Step 4.

  WilkesAsyncBurn(); // Init the library
  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var FuncEach = function()
  {
    if(workCounter%1000==0)
    {
      var s = "<div></div>";
      var div = jQuery("*[class~=r1]");
      div.append(s);
    }
    workCounter++;
  };
  var FuncFinal = function()
  {
    var ms = (new Date()).getTime() - startTime.getTime();
    console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
  };
  WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50);

// prints: "done: workCounter=20000000 time=149303ms"
// Also appends a few thousand divs to the html page, about 20 at a time.
// The browser is responsive the entire time, mission accomplished

// LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through 
// an array summing the numbers, then just putting it in an "each" function is going to kill you. 
// You can still use the concept here, but your "each" function should also have a for loop in it 
// where you burn a few hundred items manually.  
///////////////////////////////////////////////
rocketsarefast
  • 4,072
  • 1
  • 24
  • 18
-1

I have found this tutorial that explain exactly what I needed. I hope this will also help somebody else. http://nodetuts.com/tutorials/19-asynchronous-iteration-patterns.html#video

Ofri
  • 474
  • 5
  • 13