Nils
Nils

Reputation: 13

JS - Calling class methods inside of callback function

I'm currently having a problem and don't know how to solve it correctly.

I'm developing a web application with js using the WebMidi API.

I want to load all midi devices into a dropdown after WebMidi is loaded. As far as I understood it I have to pass the enable function of Webmidi a callback function:

WebMidi.enable(function (err) {
  if (err) {
    console.log("WebMidi could not be enabled.", err);
  } else {
    console.log("WebMidi enabled!");
  }
});

The problem I am having is I cannot reference class methods inside the callback function, but I have to call the method that's fills the dropdown list with all midi inputs, as soon as WebMidi is enabled.

My code looks like this:

class MidiListener{
    constructor(){
        this.enable();
        this.fillDropdown(Webmidi.inputs);
    }


    enable(){
        WebMidi.enable(function (err) {
            //Enable WebMidi
            if (err) {
              console.log("WebMidi could not be enabled.", err);
              window.alert("WebMidi is not supported by your Browser.")
            } 
            else {
                console.log("WebMidi enabled!");
            } 
        });
    }
...

The problem is fillDropdown() is called before WebMidi finished activating, and the array is empty at the time. Can I somehow reference class methods inside the callback function?

Upvotes: 1

Views: 110

Answers (1)

Eldrax
Eldrax

Reputation: 549

WebMidi.enable is an asynchronous function, meaning it allows you to specify what to do once WebMidi is enabled. The problem is that you're not using this functionality to full power.

Imagine you're the guy who shoots the gun to start a race: your code is the equivalent of shooting and turning your eyes away from the race but still wanting to know when the race ends. If you want to do something right after the race ends, better look at the race, right?

Right now, the constructor function starts the WebMidi.enable race and immediately tries to fill the dropdown. That's not what you want - you want to fill the dropdown when the race ends.

To resolve your problem, make MidiListener.enable take a callback function that gets executed when WebMidi.enable is done without errors. This will allow you to do something right when WebMidi.enable finishes.

class MidiListener {
    constructor() {
        this.enable(() => {
            this.fillDropdown(WebMidi.inputs);
        });
    }

    enabledTest(err) {
        if(err) console.log("F");
        else console.log("Yeeee");
    }

    enable(callback) {
        WebMidi.enable(function (err) {
            //Enable WebMidi
            if (err) {
                // An error occured while starting WebMidi
                console.log("WebMidi could not be enabled.", err);
                window.alert("WebMidi is not supported by your Browser.")
            } 
            else {
                // WebMidi enabled successfully
                callback();
            } 
        });
    }

Now, this.fillDropdown will be executed only after WebMidi starts successfully. With the above analogy, the constructor function now says "fill the dropdowns when the WebMidi.enable race ends".

Also, note that I used an arrow function to create the callback function. This is important because the this keyword, when used with regular function-keyword functions, acts like a special keyword. What this refers to depends on where it's called - which in your case is somewhere in WebMidi's code.

To avoid that, use an arrow function which treats this like a regular variable with regular scoping rules. There are other workarounds too - this answer explains things very well.

Upvotes: 1

Related Questions