channae
channae

Reputation: 1009

Cannot read given property of undefined

I'm calling this.getAllJobsAPI(); in the render function.

This successfully calls the getAllJobsAPI (as given below) and I'm able to retrieve job name from the JSON array of jobs.

getAllJobsAPI(){
var headers = new Headers();
headers.append('Authorization', 'Basic YWRtaW46YWRtaW4=');

fetch('http://localhost:8080/api/json', {headers: headers})
.then((result) => {
  // Get the result
  // If we want text, call result.text()
  return result.json();
}).then((jsonResult) => {
  // get all jobs 
  var jobs = [];

  Object.keys(jsonResult.jobs).forEach(function(key) {
    jobs.push(jsonResult.jobs[key].name);

    var job = jsonResult.jobs[key].name;
    console.log(job);

    this.getLastBuildAPI(job);   

});

//var a = 'meetup';
//this.getLastBuildAPI(a);
console.log(jsonResult);

})
}

But calling this.getLastBuildAPI(job); within the for each loop throws "Uncaught (in promise) TypeError: Cannot read property 'getLastBuildAPI' of undefined" error. But If I hardcode a name variable in the end of the function and call this.getLastBuildAPI(a); I could get the success response.

Could someone tell me why I'm not able to call this.getLastBuildAPI(job); within for-each of the JSON array? Note that I'm able to see job variable in console.

Thanks,

Upvotes: 1

Views: 600

Answers (2)

Adelin
Adelin

Reputation: 8209

Because this inside the resolve callback is undefined (as it's a different context), while when it's outside the callback this is the object you expect. You can reference this var storing it first.

getAllJobsAPI(){
var headers = new Headers();
headers.append('Authorization', 'Basic YWRtaW46YWRtaW4=');
var self = this;

fetch('http://localhost:8080/api/json', {headers: headers})
.then((result) => {
  // Get the result
  // If we want text, call result.text()
  return result.json();
}).then((jsonResult) => {
  // get all jobs 
  var jobs = [];

  Object.keys(jsonResult.jobs).forEach(function(key) {
    jobs.push(jsonResult.jobs[key].name);

    var job = jsonResult.jobs[key].name;
    console.log(job);

    self.getLastBuildAPI(job);   

});

//var a = 'meetup';
//this.getLastBuildAPI(a);
console.log(jsonResult);

})

You can also apply another approaches.

1. Use bind method.

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Object.keys(jsonResult.jobs).forEach(function(key) {
    jobs.push(jsonResult.jobs[key].name);
    var job = jsonResult.jobs[key].name;
    console.log(job);
    this.getLastBuildAPI(job);   
}.bind(this));

2. Use arrow functions.

Until arrow functions, every new function defined its own this value.

For instance, this can be a new object in the case of a constructor.

function Person(age){
  this.age=age;
  console.log(this);
}
let person=new Person(22);

Or this can points to the base object if the function created can be accessed like obj.getAge().

let obj={
  getAge:function(){
    console.log(this);
    return 22;
  }
}
console.log(obj.getAge());

An arrow function does not create its own this, it's just used the this value of the enclosing execution context. In the other hand, arrow function uses this of parent scope.

Object.keys(jsonResult.jobs).forEach((key) => {
jobs.push(jsonResult.jobs[key].name);
   var job = jsonResult.jobs[key].name;
   console.log(job);
   this.getLastBuildAPI(job);   
});

Upvotes: 3

Titus
Titus

Reputation: 22474

You've passed a normal function to forEach which changed the context. If you want to preserve the context use an arrow function or, alternatively, you could create a new variable to keep a reference to this.

Using an arrow function:

Object.keys(jsonResult.jobs).forEach(key => {
    .....
    this.getLastBuildAPI(job); 
});

Keeping a reference to this:

var that = this;
Object.keys(jsonResult.jobs).forEach(function(key) {
     ....
    that.getLastBuildAPI(job); 
});

Upvotes: 2

Related Questions