-1

I am trying to assign a object to a prototype, but I keep getting a error that the object is undefined. I am trying to do this

//x.prototype.y = {};
StreetEasy.prototype.neighborhoodPaths = {};

and at a later point in the code I am trying to access this object in another prototype function and push in new data with

//x.prototype.assignData = function(z, a){
  // this.y[z] = a;
//}
StreetEasy.prototype.findNeighborhoodPath = function(areaQuery, callback){
  var data = {q: areaQuery, key: this.apiKey, format: this.format};
  var query = qs.stringify(data);
  var url = AreaSearchUrl + '?' + query;

  var areaQ = areaQuery.toLowerCase();
  if (this.neighborhoodPaths[areaQ]) {
      callback(this.neighborhoodPaths[areaQ].path);
   } else {
      http.get(url, function(response){
        response.setEncoding('utf8');
        response.on('data', function (rData){
          rData = JSON.parse(rData);
          callback(rData);
          rData.areas.every(function(element){
    -----------error is here-> this.neighborhoodPaths[element.name.toLowerCase()].path = element.path;
          });

        }).on('error', function(e){
               console.error('ERROR: ' + e.message + ' | ' + e.statusCode );
      });
    });
  }
};

Node keeps returning

TypeError: Cannot read property z of undefined. What am I doing wrong?

Edit more of the code:

StreetEasy.prototype.neighborhoodPaths = {}; 
StreetEasy.prototype.findNeighborhoodPath = function(areaQuery, callback){
  var data = {q: areaQuery, key: this.apiKey, format: this.format};
  var query = qs.stringify(data);
  var url = AreaSearchUrl + '?' + query;

  var areaQ = areaQuery.toLowerCase();
  if (this.neighborhoodPaths[areaQ]) {
      callback(this.neighborhoodPaths[areaQ].path);
   } else {
      http.get(url, function(response){
        response.setEncoding('utf8');
        response.on('data', function (rData){
          rData = JSON.parse(rData);
          callback(rData);
          rData.areas.every(function(element){
    -----------error is here-> this.neighborhoodPaths[element.name.toLowerCase()].path = element.path;
          });

        }).on('error', function(e){
               console.error('ERROR: ' + e.message + ' | ' + e.statusCode );
      });
    });
  }
};
Mike F
  • 309
  • 1
  • 8
  • 18
  • 1
    I couldn't reproduce the problem. Please show the actual code. – thefourtheye May 10 '15 at 07:24
  • 1
    What is the variable `x`? – jfriend00 May 10 '15 at 07:24
  • Maybe x is not a function or you pass assignData as a callback/event handler. How to use prototype and the value of this is explained here: http://stackoverflow.com/a/16063711/1641941 you could try some console logs to see more output (like console.log(this)) – HMR May 10 '15 at 07:31

1 Answers1

3

(See update below, now that you've posted code.)

This is most likely a result of the way you're calling assignData. This would work, for instance:

var obj = new x();
obj.assignData("foo", "bar");

This would not:

var obj = new x();
f = obj.assignData;
f("foo", "bar");

And for the same reason, this would not:

callMeBack(obj.assignData, "foo", "bar");
function callMeBack(method, z, a) {
    method(z, a);
}

In both cases, the problem is that this within the call to assignData doesn't refer to the object, it's the global object instead. When you call a function not through a property reference, this is set to the global object (in loose mode; in strict mode it would be undefined). It's the action of calling it through a property reference that sets this to the object the property comes from.

More (on my blog):


Update, now that you've posted code. I'm going to assume findNeighborhoodPath is the function you called assignData in your original question.

The problem is indeed that you're losing this, but not quite in the way above.

The callback you're passing http.get will get called with this referring to the global object, not the instance on which findNeighborhoodPath was called; similarly, the callback you're passing into Array#every will have this referring to the global object, because you didn't tell it to do something else.

If you want to use that instance, the simplest way is to declare a variable referring to it, then use the variable:

var self = this; // <== Variable to remember `this`
http.get(url, function(response) {
    response.setEncoding('utf8');
    response.on('data', function(rData) {
        rData = JSON.parse(rData);
        callback(rData);
        rData.areas.every(function(element) {
            self.neighborhoodPaths[element.name.toLowerCase()].path = element.path;
        //  ^^^^ --- using it
        });

    }).on('error', function(e) {
        console.error('ERROR: ' + e.message + ' | ' + e.statusCode);
    });
    // ...
});

Alternately, you could use Function#bind and the thisArg argument of Array#every:

http.get(url, function(response) {
    response.setEncoding('utf8');
    response.on('data', function(rData) {
        rData = JSON.parse(rData);
        callback(rData);
        rData.areas.every(function(element) {
            this.neighborhoodPaths[element.name.toLowerCase()].path = element.path;
        }, this); // <=== Note passing `this` as the second arg to `every`

    }).on('error', function(e) {
        console.error('ERROR: ' + e.message + ' | ' + e.statusCode);
    });
    // ...
}.bind(this)); // <=== Note the .bind(this)

In the next version of JavaScript, ECMAScript6, we'll have "fat arrow" functions which have "lexical this binding," a fancy term meaning that this within the function will be the same as this was in the context in which the function was created. So once V8 supports them, and Node updates to using that version of V8 (both of those will be quite soon), you'll be able to do this (note the two => below):

http.get(url, (response) => {
    response.setEncoding('utf8');
    response.on('data', function(rData) {
        rData = JSON.parse(rData);
        callback(rData);
        rData.areas.every((element) => {
            this.neighborhoodPaths[element.name.toLowerCase()].path = element.path;
        });

    }).on('error', function(e) {
        console.error('ERROR: ' + e.message + ' | ' + e.statusCode);
    });
    // ...
});
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thank you. Are there other ways to pass the correct "this"? Would it be more efficient to use http.get.bind(this)? – Mike F May 10 '15 at 07:41
  • @MikeF: It's unlikely to matter whether you use the `self` thing or `Function#bind` and the `thisArg` argument to `Array#every` (I've added an example if you want to do that), the efficiency of that code is going to be driven by the HTTP operation, not the JavaScript. My instinct is that if you wanted to micro-optimize (which I don't recommend), the most efficient thing would be `var paths = this.neighborhoodPaths;` prior to the `http.get` call, and then `paths[element.name.toLowerCase()].path = element.path;` in the `every` callback. But I very much doubt it would matter in the real world. – T.J. Crowder May 10 '15 at 08:00