Losing access to functions within a JS class when running a callback

136 Views Asked by At

I'm working on a TVML/TVJS app and have run into an issue when working with classes in Javascript.

My starting application.js file calls a function

resourceLoader = new ResourceLoader();
resourceLoader.getHomeScreen();

I have a separate file called ResourceLoader.js with the following:

class ResourceLoader {

getHomeScreen() {
    var homeData = BASEURL + "/home.json";
    var homeTemplate = BASEURL + "/home.tvml";

    this.getRemoteJSON(homeData, homeTemplate, this._jsonLoaded);
}

_jsonLoaded(template, jsonString) {
    var parsedJSON = JSON.parse(jsonString);
    this.getRemoteXMLFile(template, parsedJSON);
}

getRemoteJSON(file, template, callback) {
    var xhr = new XMLHttpRequest();
    xhr.responseType = "text";
    xhr.addEventListener("load", function() {
        callback(template, xhr.responseText);
    }, false);
    xhr.open("GET", file, true);
    xhr.send();
}

getRemoteXMLFile(template, json) {
    var xhr = new XMLHttpRequest();
    xhr.responseType = "xml";
    xhr.addEventListener("load", function() {
        loadRemoteFile(xhr.responseText, json);
    }, false);
    xhr.open("GET", template, true);
    xhr.send();
}

}

(BASEURL is a global variable defined in application.js)

Everything works fine in the beginning. getHomeScreen() is called, it manages to call getRemoteJSON() and pass _jsonLoaded() as the callback. getRemoteJSON does the AJAX call and then runs the callback. This is where the issue happens. Once inside _jsonLoaded, "this" becomes undefined, so when I call this.getRemoteXMLFile, I get the error message "undefined is not an object (evaluating 'this.getRemoteXMLFile')"

Why was "this" working in getHomeScreen() but not in _jsonLoaded()? How can I access my getRemoteXMLFile function from _jsonLoaded()?

Thanks for any help.

2

There are 2 best solutions below

0
On

That is because callback is not a function.

A good solution would be to use ES6 Promises.

Here's an example fetching data using AJAX with a Promise

getJson(url) {
    return new Promise(
        (resolve, reject) => {
            this.get(url).then(
                (value) => {
                    resolve(JSON.parse(value));
                },
                function (reason) {
                    console.error(reason);
                    reject(new Error(reason));
                }
            )
        }
    )
}

And how to call it

getJson('http://jsonplaceholder.typicode.com/posts/1').then(
    (value) => {
        console.log(value);
    },
    function (reason) {
        console.error(reason);
    }
);
0
On

Why was "this" working in getHomeScreen() but not in _jsonLoaded()? How can I access my getRemoteXMLFile function from _jsonLoaded()?

It's technically not specific to TVML app but more of a general JS question, so let me answer this (no pun intended) in the light of JavaScript.

When you call getHomeScreen you call it on the object instance resourceLoader.getHomeScreen(), and hence the this context is preserved. But when you pass the _jsonLoaded function as a callback argument of the function and do callback(template, xhr.responseText), the object context is lost.

The recently introduced JavaScript class keyword might be confusing for someone with pure OOP language backgrounds, as it's not a object-oriented inheritance, but merely a syntactical sugar to the age old prototypal inheritance in JavaScript. So if one happen to belong to the same group, it's advisable to understand the differences and gotchas.

Having said that, one straight and simple solution to your immediate problem statement is to bind the this context whenever you pass around the function as callback.

this.getRemoteJSON(homeData, homeTemplate, this._jsonLoaded.bind(this));

Side note: Have you tried atvjs framework for building TVML apps? It lets you build and quickly prototype apps without much noise, abstracting the underlying hassles and complexities of a conventional TVML app.