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?

5
  • 1
    use arrow functions Commented Sep 20, 2019 at 17:30
  • Thanks, @Dimensionless. Please elaborate. Commented Sep 20, 2019 at 17:31
  • 1
    Either you will have to manually bind this to all the methods inside the class to have access to this as suggested by @Taki in his answer or use arrow function func = () => {} which will automatically do that for you. Commented Sep 20, 2019 at 17:32
  • Sorry, @Dimensionless. Do you mind providing a concrete example from my code? Thanks Commented Sep 20, 2019 at 17:35
  • 1
    you can also get rid of of this.instance = this as that will be redundant. Commented Sep 20, 2019 at 17:51

2 Answers 2

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);
  }
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, @Taki; that fixed it. What is bind doing?
@Alex i should've elaborated more on that, I updated the answer with a link with a good explanations
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.

1 Comment

Good explanation, @Dimensionless.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.