ConfusedPanda
ConfusedPanda

Reputation: 35

How to run multiple timer functions, one after the other in vanilla javascript

Lets say I want to run multiple timer functions, one after the other i.e firstly one function runs for 5 mins then after the completion of the first countdown, another timer begins to run for another 2 mins.

I implemented the timer function as below

function timer(count) {
    console.log(count)
    let counter = setInterval(() => {
        count = count - 1;
        if (count < 0) {
            clearInterval(counter);
            return;
        }
        console.log(count)
    }, 1000);
}

Then when I call this function twice with different arguments as

timer(15);
timer(5);

I get the output as

15
5
14
4
13
3
11
1
10
0
9
8
.
.
0

However my desired output is

15
14
.
.
2
1
0
5
4
3
2
1
0

Upvotes: 1

Views: 3420

Answers (4)

VLAZ
VLAZ

Reputation: 29009

The problem is that your timer function immediately starts a timer. Timers are asynchronous, so when you call it twice, you just start two timers immediately and they run in parallel.

If you want something to happen after one is finished, then you have to explicitly say so. You have two options:

Callback

This is slightly "older" style of dealing with async code. You call a function that will do something later and then give it a function as a parameter for what to do after it's done:

function timer(count, callback = () => {}) { //<-- take callback
 //if callback is not supplied, it's going to be an empty function
       
    console.log(count)
    let counter = setInterval(() => {
        count = count - 1;
        if (count < 0) {
            clearInterval(counter);
            callback(); //<-- run callback after this timer is finished
            return;
        }
        console.log(count)
    }, 1000);
}

//run a timer for 15 then a timer for 5
timer(15, () => timer(5));

This works but excessive use of callbacks can lead to what's known as callback hell. The example here is prone to it as well, for example if you want to run a timer for 5, then for 4, then for 3, then for 2, then for 1, you end up with this:

timer(5, 
  () => timer(4, 
    () => timer(3, 
      () => timer(2, 
        () => timer(1)
      )
    ) 
  )
);

Or in one line (for fun):

timer(5, () => timer(4, () => timer(3, () => timer(2, () => timer(1)))));

Promise

Promises are a newer way of handling async operations and help keep the code cleaner.

function timer(count) {
  console.log(count)

  return new Promise(resolve => { //return a Promise
    let counter = setInterval(() => {
      count = count - 1;
      if (count < 0) {
        clearInterval(counter);
        resolve(); //it is resolved when the count finishes
        return;
      }
      console.log(count)
    }, 1000);
  });
}

//run a timer for 15 then a timer for 5
timer(15)
  .then(() => timer(5));

One thing Promises help out is the callback hell, now if you want to run a timer for 5, then for 4, then for 3, then for 2, then for 1, you get a much more reasonable code:

timer(5)
  .then(() => timer(4))
  .then(() => timer(3))
  .then(() => timer(2))
  .then(() => timer(1));

No more nesting.

async/await

This is actually Promises again. But wearing a disguise. If a function returns a Promise, you can await it which waits until the Promise is resolved, then executes the next lines of code. You can only use await inside an async function, however since await actually transforms your code to a Promise behind the scenes. Functionally, there is little difference but you can just structure your code differently:

function timer(count) {
  console.log(count)

  return new Promise(resolve => { //return a Promise
    let counter = setInterval(() => {
      count = count - 1;
      if (count < 0) {
        clearInterval(counter);
        resolve(); //it is resolved when the count finishes
        return;
      }
      console.log(count)
    }, 1000);
  });
}

//run a timer for 15 then a timer for 5
async function main() { //you can only use `await` in async funcions 
  await timer(15);
  await timer(5);
}
/* the above will actually be transformed behind the scenes into:
timer(15)
  .then(() => timer(5));
*/

main();

Upvotes: 5

nick zoum
nick zoum

Reputation: 7295

You have to wait for the first call to finish before doing the second call. An easy way to do that is by wrapping the setInterval call with a Promise and call resolve when the counter reaches 0.

Using Promise (ES6 but runs on most browsers, needs Polyfill for IE)

timer(15).then(timer.bind(0, 5));

function timer(count) {
  console.log(count);
  return new Promise(function(resolve) {
    var intervalID = setInterval(function() {
      count = count - 1;
      if (count < 0) {
        clearInterval(intervalID);
        resolve();
        return;
      }
      console.log(count);
    }, 1000);
  });
}

Using async and await (ES7)

(async function() {
  await timer(15);
  await timer(5);
})();

async function timer(count) {
  console.log(count);
  return new Promise(resolve => {
    let intervalID = setInterval(() => {
      count = count - 1;
      if (count < 0) return resolve(), clearInterval(intervalID);
      console.log(count);
    }, 1000);
  });
}

Upvotes: 1

Pedro Lima
Pedro Lima

Reputation: 1606

It appears you wanted the second timer function to be executed only when all the intervals from the first one are completed. This is absolutely not what you implemented. As it is, the first function is called, which sets the interval, and finishes. The main code is not interrupted by the setting of the interval, so it will simply keep executing, calling the second timer function.

According with your desired output, you will certainly need an async function, to know when the first interval is done executing. Here is what you can try:

function timer(count) {
   return new Promise(resolve => {
       console.log(count)
        let counter = setInterval(() => {
            count = count - 1;
            if (count < 0) {
                clearInterval(counter);
                resolve();
                return;
            }
            console.log(count)
        }, 1000);
    }
}

timer(15).then(() => {
    timer(5);
});

If you are already in an async function, the last bit can also be written as:

await timer(15);
timer(5);

You should give a read about async functions. It's really worth it.

Upvotes: 0

Lux
Lux

Reputation: 18240

The best way to work with async stuff in modern javascript are promises and async/await. But because there is no default way to wait we need to write it ourself. Thats pretty simple tho:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

Now we can use this to write your timer with async/await and make it return a Promise:

async function timer(count) {
    for(let i = count; i > 0; i--) {
        await wait(1000);
        console.log(i);
    }
}

And not its easy to use timer multiple times in an async function:

await timer(15);
await timer(5);

So the full code is:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function timer(count) {
    for(let i = count; i > 0; i--) {
        await wait(1000);
        console.log(i);
    }
}

async function multipleTimers() {
    await timer(15);
    await timer(5);
}

multipleTimers();

Upvotes: 0

Related Questions