0

I am having a constructor function, which in the end should return a couple of methods. Currently I struggle to understand how I can use the value from the

var info = JSON.parse(xhr.responseText);

in the method, called getNames. It works if I combine the two methods to one, so the code itself works, only problem is to pass the variable.

function TestConstructor(url) {
  var newObject = {};
  this.newObject = newObject;

First method in the constructor function:

  newObject.load = function () {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://notarealurl.com/104857.json");
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4 && xhr.status === 200) {
        var info = JSON.parse(xhr.responseText);
        return info;
      }
     };
     xhr.send();
   };

Second method in the constructor function:

 newObject.getNames = function (info) {
   var kommuner = info.elements;
   var result = [];
   for (i = 0; i < Object.keys(kommuner).length; i++) {
      result.push(Object.keys(kommuner)[i]);
      }
   return result;
 };

return newObject;
}

When I try to

console.log(newObject.getNames(info)); 

I receive error message

Uncaught TypeError: Cannot read property 'elements' of undefined
at Object.TestConstructor.newObject.getNames 

Sorry if similar question has been asked earlier, I have looked at a few without understanding how it solves my problem. I have also tried to look at the callback function, but I struggle to understand how it works in my case. Any help appreciated, thanks :)

pythonstud
  • 41
  • 5
  • 1
    `getNames` accepts a parameter, the `info`. If you don't pass a parameter and then try to access `info.elements`, an error will be thrown, because `info` is `undefined` – CertainPerformance Apr 20 '19 at 19:26
  • Right, but how can I pass the value from the info var in the first method? I dont know how to access it – pythonstud Apr 20 '19 at 19:31
  • I'd suggest using a Promise instead of `XMLHttpRequest`, then you can call `.then` on the Promise, and pass the resolved value to `getNames` – CertainPerformance Apr 20 '19 at 19:32

1 Answers1

1

I think, the behavior you are looking for is to store the "info" in a variable local to your constructed object, and access that variable from the second method. So, why not put a var info = null; in your constructor. Then, after parsing the data, assign it to that variable (i.e. remove the var). And, instead of expecting an argument info, you can just use that local variable.

function TestConstructor(url) {
  var newObject = {};
  var info; // <-- local variable available to both functions.
  this.newObject = newObject;

  newObject.load = function () {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://notarealurl.com/104857.json");
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4 && xhr.status === 200) {
        info = JSON.parse(xhr.responseText); // <-- assign result to local variable
      }
    };
    xhr.send();
  };

  newObject.getNames = function() { // <-- no argument here
    var kommuner = info.elements; // <-- instead, use local variable
    var result = [];
    for (i = 0; i < Object.keys(kommuner).length; i++) {
      result.push(Object.keys(kommuner)[i]);
    }
    return result;
  };

  return newObject;
}

This is, however, not a very clean solution. Instead, as CertainPerformance suggested, you should use the Promise pattern to provide your data asynchronously, since you currently don't know, when your load call has actually completed. Therefore, info may not even be initialized, when you call getNames.

A promise based solution could look like this:

    function TestConstructor() {
      var newObject = {},
          info;

      newObject.load = function () {
        var p = new Promise(function(resolve, reject) {
          var xhr = new XMLHttpRequest();
          xhr.open("GET", "http://notarealurl.com/104857.json");
          xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
              if (xhr.status === 200) { // <-- call succeeded, parse result
                info = JSON.parse(xhr.responseText); // <-- assign result to local variable
                resolve(info); // <-- resolve promise with the result
              } else {
                reject(); // <-- call failed, reject the promise
              }
            }
          };
          xhr.send();
        });
        return p;
      };

      newObject.getNames = function() { // <-- no argument here
        var kommuner = info.elements; // <-- instead, use local variable
        var result = [];
        for (i = 0; i < Object.keys(kommuner).length; i++) {
          result.push(Object.keys(kommuner)[i]);
        }
        return result;
      };

      return newObject;
    }

    var myObj = new TestConstructor('');
    myObj.load().then(function() {
      var names = myObj.getNames();
      // do something with the result.
      console.log(names);
    }).catch(function() {
      // handle error graciously.
      console.warn('Failed');
    });
Christoph Herold
  • 1,799
  • 13
  • 18
  • Thanks for the tip. However, I tried to do that now, and I get the same error.Added var info = null; Removed var from the previous var info. Also removed the expected argument from the second method. – pythonstud Apr 20 '19 at 19:37
  • 1
    Are you sure, the call to load has completed, before you call `getNames`? – Christoph Herold Apr 20 '19 at 19:37
  • No, I don't know if it has. How can I check that? – pythonstud Apr 20 '19 at 19:38
  • That's exactly where promises come in :-) Anyways, please just check in your browser's console, if a later manual call to `getNames` returns the desired result. If not, something else is wrong. If so, you should introduce the promise pattern for loading the data. For more information on Promises, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise, or of course the answer from the duplicate question. It contains a heck of a lot of information on the subject :-) – Christoph Herold Apr 20 '19 at 19:41
  • How did I not think about checking that in the console... You were right. By calling it later I got the desired result. Thank you! Do you have any tips regarding how to delay the getNames method? I tried to look up the callback(), but didn't really understand how I could use it. – pythonstud Apr 20 '19 at 19:46
  • 1
    I have added the promise based solution. I can only urge you to get very much acquainted with Promises in JS, because you will encounter them everywhere, and they make your life so much easier. The basic idea is, that you get an object, which exposes events, to which you can subscribe. Those events are `.then` (the operation is finished and the result is available), `.catch` (the operation failed, and you can get a reason), and `.finally` (something to be done in both cases). But, that's just scratching the surface. – Christoph Herold Apr 20 '19 at 19:58
  • Very much appreciated, thank you so much! Will take a deeper look at Promises, seems to be worth the time :) – pythonstud Apr 20 '19 at 20:46