0

I am trying to make a class where a filename is passed to the constructor, the file is loaded using an XmlHttpRequest, and the result is stored in a class variable. The issue is that request.onreadystatechange is an asynchronous function and therefore the method getData ends up being called before the constructor completes. The result of the data is therefore undefined. How can I make sure the methods of this class wait for the file to be loaded before running. I'm rather new to async code and promises so please explain like I'm 5.

Wrapping the method in a timeout as shown in my code fixes the issue somewhat but that is an awful solution and the synchronous version of request.open is depreciated because of obvious performance issues so that is not a solution either unfortunately.

test.html

<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <script src="test.js"></script>
        <script>
            let test = new testClass('test.json')
            console.log(test.getData)
        </script>
    </body>
</html>

test.js

class testClass {
    constructor(filename) {
        this.loaded_data = undefined

        let request = new XMLHttpRequest()

        request.overrideMimeType('application/json')
        request.open('GET', filename)
        request.onreadystatechange = () => {
            if (request.status == '200' && request.readyState == 4) {
                /* Load and parse JSON here */
                this.loaded_data = 'foo'
            }
        }

        request.send()
    }

    get getData() {
        setTimeout(() => {
            console.log(this.loaded_data)
        }, 500);

        return this.loaded_data
    }
}
jerboa88
  • 450
  • 1
  • 7
  • 9
  • 4
    The solution is [not to place async code in a class constructor](https://stackoverflow.com/q/24398699/1048572) – Bergi Jun 30 '19 at 20:45
  • 1
    Alternatively you could make `this.loaded_data` contain a promise for the data, but that would imply that all methods using it would be asynchronous and promise-returning themselves, making your class awkward to use. – Bergi Jun 30 '19 at 20:47

1 Answers1

1

From a design perspective, the File itself is not "asynchronous", what is asynchronous is loading that file. Therefore, your class should only represent an already loaded file:

  class File {
    constructor(content) { this.content = content; }

Now loading that file should be a static, asynchronous method:

   static load(path) {
      return fetch(path) /*¹*/
         .then(res => res.json())
         .then(content => new File(content));
   }
 }

That can then be used as:

 (async function() {
   let file = await File.load('test.json')
   console.log(file.content);
 })()

¹: fetch() is way easier to use than XmlHttpRequest, as it returns a Promise (and thus nicely works with async / await) and it provides some utilities to parse / work with the response. In this case, the response is parsed as JSON.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151