Search code examples
javascriptsettimeoutes6-promise

Loop setTimeout with Promise


To ensure that execution duration is shorter than interval frequency, I a recursive pattern for the setTimeout function. But if I combine it with Promise the output "The End" is executed before the loop ends. How to use Promise correctly, or is Promise the wrong pattern for my needs?

function awaitTimeout(delay) {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, delay);
  });
}

function loop(msg, i) {
  if (i == msg.length) return;
  awaitTimeout(200)
    .then(function () {
      console.log(msg.substring(0, i));
      loop(msg, i + 1);
    })
    .catch();
}

awaitTimeout(3000)
  .then(function () {
    loop("Hello World, this is a Loop.", 1);
    console.log("The End");
  })
  .catch();

Solution

  • You really only have two problems...

    1. loop should resolve with the recursively produced promise. It can do so by returning the result of the recursive call.
    2. Your "The End" logger needs to wait for the async loop to complete.

    Also, using await within async functions typically makes for cleaner code IMO.

    const awaitTimeout = (delay) => new Promise((r) => setTimeout(r, delay));
    
    // Async function
    const loop = async (msg, i) => {
      if (i == msg.length) {
        return;
      }
      await awaitTimeout(20); // shortened for the snippet
      console.log(msg.substring(0, i));
      return loop(msg, i + 1); // return the recursive result
    };
    
    awaitTimeout(0) // shortened for the snippet
      .then(() => loop("Hello World, this is a Loop.", 1))
      .then(() => { // wait for it all to complete
        console.log("The End");
      });


    Without using async functions, loop would look like this

    const loop = (msg, i) => {
      if (i == msg.length) {
        return;
      }
      return awaitTimeout(200).then(() => { // return a promise
        console.log(msg.substring(0, i));
        return loop(msg, i + 1); // return the result
      });
    };