How to stop asynchronous function in JavaScript?

前端 未结 1 833
野性不改
野性不改 2021-01-25 09:15

I have some asynchronous problems. I\'m working on an ECMAScript 6 object. It\'s a timer and I want to be able to restart during its countdown.

Here is my work:

相关标签:
1条回答
  • 2021-01-25 09:29

    An easier way to know if the timer is "running" is to perhaps use setInterval instead.

    var interval = setInterval(() => updateTimer(), 10); // update every 10ms
    

    It's running if interval is set

    if (interval) // timer is running
    

    Stop the timer

    window.clearInterval(interval);
    interval = null;
    // timer is no longer "running"
    

    Additional notes

    Beware of creating timers that increment with a fixed value

    In your code, you have

    setTimeout(() => this.count--, 1000);
    

    The intention is for you to decrement your count property once every second, but this is not the behavior you will be guaranteed.

    Check out this little script

    var state = {now: Date.now()};
    
    function delta(now) {
      let delta = now - state.now;
      state.now = now;
      return delta;
    }
    
    setInterval(() => console.log(delta(Date.now())), 1000);
    
    // Output
    1002
    1000
    1004
    1002
    1002
    1001
    1002
    1000
    

    We used setInterval(fn, 1000) but the actual interval varies a couple milliseconds each time.

    The problem is exaggerated if you do things like switch your browser's focus to a different tab, open a new tab, etc. Look at these more sporadic numbers

    1005 // close to 1000 ms
    1005 // ...
    1004 // a little variance here
    1004 // ...
    1834 // switched focus to previous browser tab
    1231 // let timer tab run in background for a couple seconds
    1082 // ...
    1330 // ...
    1240 // ...
    2014 // switched back to timer tab
    1044 // switched to previous tab
    2461 // rapidly switched to many tabs below
    1998 // ...
    2000 // look at these numbers...
    1992 // not even close to the 1000 ms that we set for the interval
    2021 // ...
    1989 // switched back to this tab
    1040 // ...
    1003 // numbers appear to stabilize while this tab is in focus
    1004 // ...
    1005 // ...
    

    So, this means you can't rely upon your setTimeout (or setInterval) function getting run once per 1000 ms. count will be decremented with much variance depending on a wide variety of factors.

    To work around this, you need to use a delta. That means before each "tick" of your timer, you need to take a timestamp using Date.now. On the next tick, take a new timestamp and subtract your previous timestamp from the new one. That is your delta. Using this value, add it to the Timer's total ms to get the precise number of milliseconds the timer has been running for.

    Then, all time-sensitive values will be a projection/calculation of the total accumulated ms.

    In your case, say you have a count that starts at 10. If you want to count down by -1 each 1000 ms, you could do

    function update() {
      // update totalMs
      this.totalMs += calculateDelta();
      // display count based on totalMS
      console.log("count %d", Math.ceil(this.count - this.totalMs/1000));
    }
    

    Here's a sample ES6 timer that implements a delta function that might help you

    class Timer {
      constructor(resolution=1000, ms=0) {
        this.ms = ms
        this.resolution = resolution;
        this.interval = null;
      }
      delta(now) {
        let delta = now - this.now;
        this.now = now;
        return delta;
      }
      start() {
        this.now = Date.now();
        this.interval = window.setInterval(() => this.update(), this.resolution);
      }
      reset() {
        this.update();
        this.ms = 0;
      }
      stop() {
        this.update();
        window.clearInterval(this.interval);
        this.interval = null;
      }
      update() {
        this.ms += this.delta(Date.now());
        console.log("%d ms - %0.2f sec", this.ms, this.ms/1000);
      }
    }
    

    Create a new timer with a 50 ms "resolution". All this means is that the timer display is updated every 50 ms. You could set this value to anything and the timer will still keep an accurate value.

    var t = new Timer(50);
    t.start();
    

    To simulate the reset, we can just create a one-off timeout so you can see the reset working

    // in ~5 seconds, reset the timer once
    setTimeout(() => t.reset(), 5000);
    

    Here's a demonstration of pausing the timer

    // in ~10 seconds, pause the timer
    setTimeout(() => t.stop(), 10000);
    

    And you can resume the timer, too

    // in ~12 seconds, resume the timer (without reset)
    setTimeout(() => t.start(), 12000);
    

    You can start, stop, reset the timer as much as you like


    Here's an the ES6 (above) transpiled to ES5 so you can see the code working in a runnable snippet. Open your console and click Run code snippet.

    "use strict";
    
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    
    var Timer = (function () {
      function Timer() {
        var resolution = arguments.length <= 0 || arguments[0] === undefined ? 1000 : arguments[0];
        var ms = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
    
        _classCallCheck(this, Timer);
    
        this.ms = ms;
        this.resolution = resolution;
        this.interval = null;
      }
    
      Timer.prototype.delta = function delta(now) {
        var delta = now - this.now;
        this.now = now;
        return delta;
      };
    
      Timer.prototype.start = function start() {
        var _this = this;
    
        this.now = Date.now();
        this.interval = window.setInterval(function () {
          return _this.update();
        }, this.resolution);
      };
    
      Timer.prototype.reset = function reset() {
        this.update();
        this.ms = 0;
      };
    
      Timer.prototype.stop = function stop() {
        this.update();
        window.clearInterval(this.interval);
        this.interval = null;
      };
    
      Timer.prototype.update = function update() {
        this.ms += this.delta(Date.now());
        console.log("%d ms - %0.2f sec", this.ms, this.ms / 1000);
      };
    
      return Timer;
    })();
    
    var t = new Timer(50);
    t.start();
    
    // in ~5 seconds, reset the timer once
    setTimeout(function () {
      return t.reset();
    }, 5000);
    
    // in ~10 seconds, pause the timer
    setTimeout(function () {
      return t.stop();
    }, 10000);
    
    // in ~12 seconds, resume the timer (without reset)
    setTimeout(function () {
      return t.start();
    }, 12000);

    0 讨论(0)
提交回复
热议问题