Executing asynchronous functions serially with a delay

272 views Asked by At

originally I have something like this (ads is an array) :

for (var i = 0; i < ads.length; i++) {
    asynchMeth(ad[i]);
}

asyncMeth is an asynchronous method that calls the server (I cannot make it synchronous). But I would like that asynchMeth(ad[i]) will start 3second after asynchMeth(ad[i-1]) has finished. The following doesn't work but it gives an idea on what I am looking for :

    isWorking = false; //Will be set to true whenever asyncMeth starts, to false when it ends.

     var i = 0;
     var timer = setInterval(3000, function() {
       if(!isWorking){
          if(i < ads.length){
               asyncMeth(ads[i]);
               i++;
           }
           else{
                clearInterval(timer);
           }
        }
    });

How to work with setInterval when we are calling function with different/dynamic parameters ?

2

There are 2 answers

0
Vivin Paliath On

I would first add a callback to asyncMeth that is called whenever the function has finished doing whatever it is doing. Otherwise there is no way to know if asyncMeth has finished its work. In your example what you have isn't the same as what you want! You want ads[i] to process 3 seconds after ads[i - 1] has finished. Instead, what you have is code to start processing ads[i] 3 seconds after ads[i - 1] has started processing. With a callback to asyncMeth, you can then do this:

(function work(i) {
    if(i < ads.length) {
        asyncMeth(ads[i], function() {
            work(++i);
        });
    }
}(0);

Here you have a self-invoked function that initially accepts a parameter value of 0. This value is assigned to the parameter i. If i is lesser than ads.length, it means that we still have items to process. So it calls asyncMeth with ads[i] as a parameter.

But then we also provide a callback. In this callback we tell the callback to call work with the incremented the value of i. This means that work will start processing the next item.

So now you can asynchronously-process each item one after the other until i becomes equal to ads.length, at which point you have processed all the items.

EDIT

I noticed that you mentioned that you want a 3-second delay. For that you can do this:

(function work(i) {
    if(i < ads.length) {
        asyncMeth(ads[i], function() {
            setTimeout(function() {
                work(++i);
            }, 3000);
        });
    }
}(0);

The only difference here is that the next iteration happens 3 seconds after the last one has completed.

0
jfmatt On

AFAICT, there are two answers to be given here: the one you're asking for, and the one you need. I'll give both in case someone else stumbles upon this answer from Google.

What you should do

It looks like you're trying to make a bunch of asynchronous calls happen in sequence, and guessing that they'll come back in about three seconds. Of course, if that turns out not to be an accurate guess, you're going to have problems, so what you really want is a function that will be triggered at the instant that the request comes back.

There are two ways to do this - either continuation-passing style (aka callback hell), or with promises. The former is easier conceptually, but gets ugly in a hurry:

$http('url', function (data) {
  //this function is called when the AJAX request comes back
  //do something with data
  $http('url2', function (data) {
     //do something with second piece of data after request 2 loads
  })
})

This gets to be a real mess when you need an arbitrary number. My preferred solution is a recursive higher-order function, such as the following, although it is possible to do it iteratively:

var launch = function f (i) {
  return function (data) {
    //do something with data

    if (i <= urls.length - 1)
      $http(urls[i+1], f(i+1))
  }
}

//manually trigger the first one
launch(0)()

The cleaner way is with promises. There are hundreds of tutorials on how to use promises to clean up async code, and here is an example of how to do it for a variable number of requests.

What you asked

If you really want to just launch a bunch of timeouts with different arguments, the trick is to close over each one to cache the value of i. If you do this:

for (var i = 0; i < 20; i++) {
  setTimeout(function () {
    //i is 20 by the time this runs...
  }, 1000)
}

then all 20 invocations of the function will see i as 20, which is probably not what you wanted.

The solution is this:

for (var i = 0; i < 20; i++) {
  setTimeout((function (j) {
    //do something with j, which is a copy of the value of i for this iteration
  })(i), 1000)
}

The reason this works is that explicitly-passed primitive function arguments (i.e. numbers) are passed by value, while "passing" a variable by closure (which is what's happening in the broken example) passes by reference, even for primitives.