16

I have the following code in my constructor:

constructor(){
for (let i = 0; i < 90; i++) {
  setTimeout(() => {
    this.counter = this.counter - 1;
  }, 1000)
 }
}

What I actually want is to display a number which counts down 90 seconds. Right now it counts down from 90 to 0 immediately

Nicole W.
  • 267
  • 1
  • 4
  • 13
  • Please note that currently none of the answers are satisfactory, based on an invalid assumption in the OP question. As has been alluded to, setting a timer for 1000ms is not a guarantee that your function will be called *exactly* 1000ms later. The timer is neither high-resolution or high-priority. There will be drift - I had to fix this exact bug in code because in CPU-starved situations the drift is painfully noticeable. You need to fetch a start time and calculate remaining time (e.g. by using Date()). Only use the interval for how often to update the display. – JackLThornton Dec 08 '22 at 22:04

4 Answers4

24

You can use setInterval instead to make the function be called every 1 second until the counter reaches 0:

class Timer {
    constructor(public counter = 90) {

        let intervalId = setInterval(() => {
            this.counter = this.counter - 1;
            console.log(this.counter)
            if(this.counter === 0) clearInterval(intervalId)
        }, 1000)
    }
}

Or if you want something that looks like a for and uses setTimeout you could use async/await and Promisses (admittedly this might be overkill for this simple example):

function delay(delay: number) {
    return new Promise(r => {
        setTimeout(r, delay);
    })
}
class Timer {
    constructor(public counter = 90) {
        this.doTimer();
    }
    async doTimer() {
        for (let i = 0; i < this.counter; i++) {
            await delay(1000);
            this.counter = this.counter - 1;
            console.log(this.counter);
        }
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 1
    I had to wait a few minutes until I was able to :) – Nicole W. Jul 31 '18 at 17:16
  • interval is not 1 second – Abdullah Nurum Feb 07 '19 at 14:54
  • @AbdullahNurum what do you mean. 1000ms = 1s https://www.w3schools.com/jsref/met_win_setinterval.asp. It might not trigger exactly at 1s because timers in js are best effort they are not a guarantee. But it should be 1s – Titian Cernicova-Dragomir Feb 07 '19 at 14:56
  • With this piece of code (top) the interval lives on even when timer instance is destroyed. – colin Jul 25 '19 at 09:46
  • @colin what do you mean by "destruction of the instance". JS does not have deterministic destruction. The instance will be garbage collected when nobody has a reference to it anymore. As long as there is a timeout scheduled, the instance can't be GCed, so it will stay alive as long as the count down is happening. – Titian Cernicova-Dragomir Jul 25 '19 at 09:51
  • Yes actually that's what I meant. How to make it GC when my code doesn't reference it anymore? – colin Jul 25 '19 at 09:53
  • @colin you should stop the countdown in some way if you don't need it anymore, while the `doTimer` method is await-ing it will not be collected, but you could break out of that `for` on some condition (`if(this.isDisposed === true) break` or something like it) and then the object will be GCed. But this only if you want to stop the count down early, otherwise, after the countdown is over, the object will be GCed as usual, you don't need anything special. – Titian Cernicova-Dragomir Jul 25 '19 at 09:56
3

Another solution I found :

counter: { min: number, sec: number }

  startTimer() {
    this.counter = { min: 30, sec: 0 } // choose whatever you want
    let intervalId = setInterval(() => {
      if (this.counter.sec - 1 == -1) {
        this.counter.min -= 1;
        this.counter.sec = 59
      } 
      else this.counter.sec -= 1
      if (this.counter.min === 0 && this.counter.sec == 0) clearInterval(intervalId)
    }, 1000)
  }

And in your html:

<span>{{counter.min}} : {{counter.sec}}</span>
1

Here is my solution:

// You can set binding in your templeteurl with this variable that will display the time couner
// for example <span id='myTimeCounter'>{{expirationCounter}}</span>
expirationCounter: string;
startTimer(secsToStart:number): void {
    var start: number = secsToStart;
    var h: number;
    var m: number;
    var s: number;
    var temp: number;
    var timer: any = setInterval(() =>
    {
        h = Math.floor(start / 60 / 60)
        // remove the hours
        temp = start - h * 60 * 60;
        m = Math.floor(temp / 60);
        // remove the minuets
        temp = temp - m * 60;
        // what left is the seconds
        s = temp;

        // add leading zeros for aesthetics
        var hour = h < 10 ? "0" + h : h;
        var minute = m < 10 ? "0" + m : m;
        var second = s < 10 ? "0" + s : s;

        this.expirationCounter = hour + ":" + minute + ":" + second;

        if (start <= 0) {
            // Time elapsed
            clearInterval(timer);
            this.expirationCounter = "Expired";
            // Make here changes in gui when time elapsed
            //....
        }
        start--;
    }, 1000)
}
Jonathan Applebaum
  • 5,738
  • 4
  • 33
  • 52
0

using setInterval() function also timmer show in descending order like 10s,9s,8s...

timeLeft: any = 10;
 startTimer() {
    setInterval(() => {
      if (this.timeLeft > 1) {
        this.timeLeft--;
      } else {
        this.timeLeft = 10;
      }
    }, 1000);
  }
Sushil
  • 670
  • 7
  • 14