3

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?

Alex
  • 34,699
  • 13
  • 75
  • 158

2 Answers2

5

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);
  }
FZs
  • 16,581
  • 13
  • 41
  • 50
Taki
  • 17,320
  • 4
  • 26
  • 47
1

In javascript classes, if you use regular old function functionName() { } the function doesn't have access to this of the class inside the function as they have their own this. You will have to manually bind this to that function inside the constructor to have access to classes this.

contructor() {
  this.decrementCounter = this.decrementCounter.bind(this);
}

As you can see this will become repetitive soon as you will have to do that for each method inside the class. To help with that you can use es6 arrow functions.

decrementCounter = () => {}

es6 arrow function automagically binds this to its parent scope so that you can access parents this inside the methods.

My explanation is not accurate but it goes like that.

I will attach a link to a better explanation when I find one.

Deiknymi
  • 196
  • 3
  • 10