0

here's the code I currently using,

function loopArrayWithAsync(array, doSthWithElement, finalCallback) {
    var count = 0;
    var _data = [];
    var _errs = [];
    for (var i = 0; i < array.length; i++) {
        doSthWithElement(array[i], function (err, data) {
            count++;
            if (err) {
                _errs.push(err);
            }
            if (data) {
                _data.push(data);
            }
            if (count === data.length) {
                finalCallback(_errs, _data);
            }
        }
    }
}

then, I will use the function in this way:

loopArrayWithAsync(array, function (element, finish) {
    // element - element in the array
    asyncFunc(element, function (err, result) {
        if (err) {
            finish(err);
        } else {
            finish(null, result);
        }
    });
}, function (errs, finalData) {
    // when the for loop is finished,
    // i.e. (count === data.length)
    // this callback will be executed
    // errs - array of err from the above callback function
    // finalData - array of result from the above callback function
    outerCallback(errs, finalData);
});

with this implementation, I can loop through an array with async function and execute the callback function when all elements in the array have been processed.

but now I want to add a delay/interval feature to loopArrayWithAsync()

something like loopArrayWithAsync(array, {interval : 1000}, function (element, finish) {..., after it processed the first element, it should wait for 1000ms, then starts to process the second element, and vice versa...

I've found another question talking about adding delay to for loop

but it seems to be more complicated while dealing with async functions.

Any answers will be appreciated

============================== update ==============================

this is the function after refactoring,

function loopArrayWithAsync(array, options, doSthWithElement, finalCallback) {
    if (isFunction(options)) {
        finalCallback = doSthWithElement;
        doSthWithElement = options;
        options = {};
    }
    options.interval = options.interval || 0;
    options.oneByOne = options.oneByOne || false;
    var _data = [];
    var _errs = [];
    var count = 0;
    var length = array.length;
    var i = 0;
    (function handleIteration() {
        if (i < length) {
            var element = array[i];
            doSthWithElement(element, function (err, data) {
                if (err) {
                    _errs.push(err);
                }
                if (data) {
                    _data.push(data);
                }
                count++;
                if (count === length) {
                    finalCallback(_errs, _data);
                } else if (options.oneByOne) {
                    if (options.interval) {
                        setTimeout(handleIteration, options.interval);
                    } else {
                        process.nextTick(handleIteration);
                    }
                }
            });
            i++;
            if (!options.oneByOne) {
                if (options.interval) {
                    setTimeout(handleIteration, options.interval);
                } else {
                    process.nextTick(handleIteration);
                }
            }
        }
    }());
};

so that I can use the function in this way now:

loopArrayWithAsync(array, {interval : 1000}, function (element, finish) {
    asyncFunc(element, function (err, result) {
        if (err) {
            finish(err);
        } else {
            anotherAsyncFunc(result, function (err, doc) {
                if (err) {
                    finish(err);
                } else {
                    finish(null, doc);
                }
            });
        }
    });
}, function (errs, finalData) {
    outerCallback(errs, finalData);
});

or

loopArrayWithAsync(array, {oneByOne : true}, function (element, finish) {... loop through the elements one by one

loopArrayWithAsync(array, {interval : 5000, oneByOne : true}, function (element, finish) {... loop through the elements one by one and with 5 seconds delay

available options :

interval is the amount of milliseconds between each iteration, default : 0

If oneByOne is true, the method would only proceed to the next element until finish has been invoked for the current element, default : false

The code suits my case now, but I will still try the suggested libraries to make life easier, thank you

Please leave a comment if you found that the code can be further improved, looking forward to any suggestions!

Community
  • 1
  • 1
kitce
  • 23
  • 5
  • 2
    Why don't use Promises? For example, [bluebird's `map`](https://github.com/petkaantonov/bluebird/blob/master/API.md#mapfunction-mapper--object-options---promise) does what you want. – thefourtheye May 23 '15 at 09:46
  • @thefourtheye Thank you for pointing me to bluebird. I didn't know about that. But, if a lib is an option, I would suggest to use async: https://www.npmjs.com/package/async#each – Peter Paul Kiefer May 23 '15 at 10:08
  • it seems not bad, I will study it in the future, thank you for the suggestion – kitce May 23 '15 at 11:18

2 Answers2

0

You can use a local function to make the asynchronous loop. For the next iteration the function calls itself with a delay:

function loopArrayWithAsync(array, doSthWithElement, finalCallback, delay) {
  var _data = [], _errs = [], i = 0;
  loop();

  function loop() {
    doSthWithElement(array[i], function (err, data) {
      if (err) {
        _errs.push(err);
      }
      if (data) {
        _data.push(data);
      }
      i++;
      if (i === array.length) {
        finalCallback(_errs, _data);
      } else {
        window.setTimeout(loop, delay);
      }
    }
  }

}

To start the calls at a certain interval instead of having a delay between calls, just use setInterval with different times:

function loopArrayWithAsync(array, doSthWithElement, finalCallback, delay) {
  var _data = [], _errs = [], count = 0;
  for (var i = 0; i < array.length; i++) {
    window.setTimeout(function() {
      doSthWithElement(array[i], function (err, data) {
        if (err) {
          _errs.push(err);
        }
        if (data) {
          _data.push(data);
        }
        count++;
        if (count === array.length) {
          finalCallback(_errs, _data);
        }
      });
    }, i * delay);
  }
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • this may works in most situations, but there is a problem, what if my `asyncFunc()` needs more time than `delay`? The next iteration will never start until `asyncFunc()` called `finish`. – kitce May 23 '15 at 11:14
  • @kitz17: The delay is from the finish of one call to the start of the next. If you just want to make new calls at a certain interval, regardless of whether the previous call has finished, that is easier. Just use `setTimeout` with a delay of `i * delay` to start them at different times. I added an example above. – Guffa May 23 '15 at 13:08
0

As suggested by @thefourtheye you can use the concept of Promises, and Bluebird is a fast and good library for this. Promise.settle lets you resolve and reject your promises and then inspect the result.

function loopArray(array) {
    var arrayOfPromises = [];
    for (var i = 0; i < array.length; i++) {
        arrayOfPromises.push(doSomethingAsync(array[i]));
    }

    Promise.settle(arrayOfPromises).then(function (results) {
        console.log("All async calls done! You can inspect the result!");
        console.log(results);
    });
}


function doSomethingAsync(item) {
    return new Promise(function(resolve, reject){
        //Do you async work here!
        console.log("Entering async function call " + item);
        if(item === "three"){
            reject("bad value!");
        }
        resolve(item + " promise done!");
    });
}


loopArray(["one","two","three"]);

I made a JSFiddle of the below example. Working with asynchronous functions, promises can help you out a lot, so I'd really suggest you look into it.

Daniel B
  • 8,770
  • 5
  • 43
  • 76