Search code examples
javascriptecmascript-5

Convert JavaScript function to class with callback methods


I'm trying to convert the following function to a class in JavaScript (original function from CMS created here),

function Countdown(options) {
  var timer,
  instance = this,
  seconds = options.seconds || 10,
  updateStatus = options.onUpdateStatus || function () {},
  counterEnd = options.onCounterEnd || function () {};

  function decrementCounter() {
    updateStatus(seconds);
    if (seconds === 0) {
      counterEnd();
      instance.stop();
    }
    seconds--;
  }

  this.start = function () {
    clearInterval(timer);
    timer = 0;
    seconds = options.seconds;
    timer = setInterval(decrementCounter, 1000);
  };

  this.stop = function () {
    clearInterval(timer);
  };
}

With this usage,

var myCounter = new Countdown({  
    seconds:5,  // number of seconds to count down
    onUpdateStatus: function(sec){console.log(sec);}, // callback for each second
    onCounterEnd: function(){ alert('counter ended!');} // final action
});

myCounter.start();

My attempt is,

Countdown: class {
    constructor(options) {
        this.seconds = options.seconds || 10;
        this.updateStatus = options.onUpdateStatus || function () { };
        this.counterEnd = options.onCounterEnd || function () { };
        this.instance = this;
        this.timer = null;
    }

    decrementCounter() {
        this.updateStatus(this.seconds);
        if (this.seconds === 0) {
            this.counterEnd();
            this.instance.stop();
        }
        this.seconds--;
    }

    start() {
        clearInterval(this.timer);
        this.timer = 0;
        this.timer = setInterval(this.decrementCounter, 1000);
    }

    stop () {
        clearInterval(this.timer);
    }
}

And calling it like this,

var counter = new Countdown({  
    seconds:5,  // number of seconds to count down
    onUpdateStatus: function(sec) {
        $("#timer").text(sec);
    }, // callback for each second
    onCounterEnd: function() {
        closeModal();
    } // final action
});
counter.start();

It's throwing this error in decrementCounter(),

Uncaught TypeError: this.updateStatus is not a function

What am I doing wrong?


Solution

  • this refers to the global object which doesn't have updateStatus, because the setInterval function doesn't preserve the this-context.

    Use bind in the constructor to fix it:

    Bind creates a new function that will have this set to the first parameter passed to bind().

    constructor(options) {
        this.seconds = options.seconds || 10;
        this.updateStatus = options.onUpdateStatus || function() {};
        this.counterEnd = options.onCounterEnd || function() {};
        this.instance = this;
        this.timer = null;
    
        this.decrementCounter = this.decrementCounter.bind(this);
      }