javascriptloopsforeachdelaypause

Add a delay after executing each iteration with forEach loop


Is there an easy way to slow down the iteration in a forEach (with plain javascript)? For example:

var items = document.querySelector('.item');

items.forEach(function(el) {
  // do stuff with el and pause before the next el;
});

Solution

  • What you want to achieve is totally possible with Array#forEach — although in a different way you might think of it. You can not do a thing like this:

    var array = ['some', 'array', 'containing', 'words'];
    array.forEach(function (el) {
      console.log(el);
      wait(1000); // wait 1000 milliseconds
    });
    console.log('Loop finished.');
    

    ... and get the output:

    some
    array          // one second later
    containing     // two seconds later
    words          // three seconds later
    Loop finished. // four seconds later
    

    There is no synchronous wait or sleep function in JavaScript that blocks all code after it.

    The only way to delay something in JavaScript is in a non–blocking way. That means using setTimeout or one of its relatives. We can use the second parameter of the function that we pass to Array#forEach: it contains the index of the current element:

    var array = ['some', 'array', 'containing', 'words'];
    var interval = 1000; // how much time should the delay between two iterations be (in milliseconds)?
    array.forEach(function (el, index) {
      setTimeout(function () {
        console.log(el);
      }, index * interval);
    });
    console.log('Loop finished.');

    Using the index, we can compute when the function should be executed. But now we have a different problem: the console.log('Loop finished.') is executed before the first iteration of the loop. That's because setTimout is non–blocking.

    JavaScript sets the timeouts in the loop, but it doesn't wait for the timeouts to complete. It just continues executing the code after the forEach.

    To handle that, we can use Promises. Let's build a promise chain:

    var array = ['some', 'array', 'containing', 'words'];
    var interval = 1000; // how much time should the delay between two iterations be (in milliseconds)?
    var promise = Promise.resolve();
    array.forEach(function (el) {
      promise = promise.then(function () {
        console.log(el);
        return new Promise(function (resolve) {
          setTimeout(resolve, interval);
        });
      });
    });
    
    promise.then(function () {
      console.log('Loop finished.');
    });

    There is an excellent article about Promises in conjunction with forEach/map/filter here.


    I gets trickier if the array can change dynamically. In that case, I don't think Array#forEach should be used. Try this out instead:

    var array = ['some', 'array', 'containing', 'words'];
    var interval = 2000; // how much time should the delay between two iterations be (in milliseconds)?
    
    var loop = function () {
      return new Promise(function (outerResolve) {
        var promise = Promise.resolve();
        var i = 0;
        var next = function () {
          var el = array[i];
          // your code here
          console.log(el);
          if (++i < array.length) {
            promise = promise.then(function () {
              return new Promise(function (resolve) {
                setTimeout(function () {
                  resolve();
                  next();
                }, interval);
              });
            });
          } else {
            setTimeout(outerResolve, interval);
            // or just call outerResolve() if you don't want to wait after the last element
          }
        };
        next();
      });
    };
    
    loop().then(function () {
      console.log('Loop finished.');
    });
    
    var input = document.querySelector('input');
    document.querySelector('button').addEventListener('click', function () {
      // add the new item to the array
      array.push(input.value);
      input.value = '';
    });
    <input type="text">
    <button>Add to array</button>