Why doesn't this work? JSON quote machine object

Why doesn't this work? JSON quote machine object
0.0 0

#1

Hi guys. I was up until 5am trying to figure this out. Any ideas why when I call quoteData.init() the quoteData is still undefined. Thank you very much for any help.

$(document).ready(function() {

  var quoteData = {
    init: function () {
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function(qData) {
        this.quoteString = qData.quote;
        this.quoteCharacter = qData.character;
      });
    },
    quoteString: "",
    quoteCharacter: ""
  };

  console.log(quoteData.init());


});

#2

When function(qData) {} is executed, this is created and its value is defined by one question asked by its wrapping function, function(qData) {}. Who called me ? Whatever object called me will become this.

In your code, function(qData) {} is not (directly) called by quoteData.It’s a lonely function nested inside $.getJSON.When it’s lonely(not directly called by an object), this refers to the global object (the window object for browsers).

Now, to solve the problem, you may want to read more about the bind() method, and generally how this works.This chapter of “you don’t know js” may help : https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md

update: a freecodecamper has written a blog article that is really nice on the subject : http://jakewiesler.com/javascript-keywords-whats-up-with-this/ I hope this will help


#3

Thank you so much! I figured it must have had something to do with the this keyword. I’ll definitely be reading up on the topic. Thanks for the links


#4

Hey again. I still seem to be stuck :frowning: I tried using call like this: quoteData.init.call(quoteData) I thought this would make the this keyword refer to the quoteData object.Preformatted text

$(document).ready(function() {

  var quoteData = {
    init: function getQuoteData() {
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function getJSON(qData){
        this.quoteString = qData.quote;
        this.quoteCharacter = qData.character;
      });
    },
    quoteString: "",
    quoteCharacter: ""
  }


console.log(quoteData.init.call(quoteData), quoteData);
});

#5

Hi again.

You don’t need to bind quoteData to the init() method.

Let’s take an example :

var myObject = {
    myFunction: someFunction,
    myString: "test"
}

function someFunction () {
    console.log(this.myString); // will print "test"

    // we define a nested function
    function nestedFunction() {
        console.log(this.myString); 
    }
    // and call it
    nestedFunction(); // will print undefined
}

myObject.myFunction(); // prints "test" then prints undefined

As you can see, when we do myObject.myFunction(), myFunction() will automatically set the value of this to be myObject, because we called myFunction with myObject, using the dot notation.

However, when nestedFunction is called, it’s not the same this.Since no object called the nestedFunction (using the dot notation), the default value of this inside nestedFunction is the global object.

I tweaked your example and added the string “test” on quoteData.quoteString :

$(document).ready(function() {

  var quoteData = {
    init: function getQuoteData() {
      console.log(this.quoteString); // prints "test"
      // here, the value of this is quoteData
      
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function getJSON(qData){
        console.log(this.quoteString) // prints undefined
        // here, the value of this is no longer quoteData
      });
      
    },
    quoteString: "test",
    quoteCharacter: ""
  }

quoteData.init();
});

What causes the second this to not be quoteData ? answer : function getJSON(qData){...} wasn’t called by quoteData.Unfortunately, you cannot execute the function this way : quoteData.getJSON(…) , so you have to figure out another way to bind quoteData to function getJSON(qData){...}.


#6

So… this actually has nothing to do with this.

AJAX calls are asynchronous. This means that when you call quoteData.init(), your get request will be sent to the server, and then your code will skip past your callback function and continue, in your case, onto your console.log. When your program receives a response from the server, then it will execute the stuff inside.

Because you haven’t received your data back from the server yet, your qData variable hasn’t been defined yet. So, at the time you’re console.logging, qData is undefined. If you make sure you console.log (or otherwise try to access the qData variable) after you’ve received a response from the server, then your qData will actually have the information you want.

So, to illustrate this, if we move your console.log into the get request, and call quoteData.init() on its own, we’d have:

$(document).ready(function() {
  var quoteData = {
    init: function () {
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function(qData) {
        this.quoteString = qData.quote;
        this.quoteCharacter = qData.character;
        console.log(qData.quote);
        console.log(qData.character);
      });
    },
    quoteString: "",
    quoteCharacter: ""
  };
  quoteData.init();
});

And in your console, you’ll now see your quote.

Until you learn about promises, the easiest way to ensure that whatever you need to do with your data is done in the callback function. (This can sometimes lead to what’s affectionately known as “callback hell,” but I’ll let you learn about that one on your own!)

Does all of that make sense? (Asynchronicity is an important concept to understand!)

Edit to add: this is also a very important concept to understand, but isn’t necessarily your issue here. :slight_smile: (Although yes, it is true that the this.quoteString inside of your get is different from quoteString later on. I’d personally recommend staying away from actually using this until you feel like you have a decent grasp of what’s going on with it, because it can be a really, really confusing thing to grasp. Normal variables are cool too! :sunglasses:


#7

Yes, you’re right.There’s an even more straightforward solution, putting all the logic inside the $.getJSON call and get rid of everything else.

[details=spoiler]```
$(document).ready(function() {
$.getJSON(“https://got-quotes.herokuapp.com/quotes”, function(qData) {
console.log(qData.character + " : " + qData.quote);
});
});


But I hoped I could make him work on the `this` concept. :sweat:

#8

Ah! Sorry to have ruined your thunder. Feel free to ignore me and carry on. :blush:


#9

First of all thank you so much @Omegga and @bethqiang for taking the time to respond. I really appreciate it. I had class to go to so wasn’t around to respond earlier. I know I could have just put quoteData where the this keyword is but I wanted to try to use this (maybe its more dynamic… although that’s not really relevant in this case).

@Omegga I’ve been trying to figure how to bind the quoteData with the getJSON(qData) function for the past couple hours. I can’t seem to get it. I’m assuming i’ll be using bind or call. And im assuming it’ll be something like:
…bind/call(quoteData)
Any hints?

@bethqiang That’s interesting about the callback function getting skipped. I don’t entirely understand why though. I saw something on promises before, but I need a little more time before I’ll get into that… I’ll check out more on asynchronicity


#10

It’s not that the callback function is skipped, per say–just that the callback function won’t run until your browser receives a response. Your browser sends the request, and then instead of waiting around and not doing anything until it gets a response, it’ll say, alright, I’ll move on for now, but I’ll come back to the callback function when I’ve gotten what I need, then I’ll execute the callback function.


#11

And im assuming it’ll be something like:
…bind/call(quoteData)

You’re very close.call is used to bind an object to a function and execute that function, whereas bind does the same thing but doesnt execute.Instead it returns a new function, with the value of this that you’ve decided to assign.

var myObject = {
    myString: "test"
}

function someFunction () {
    console.log(this.myString);
}

var anotherFunction = someFunction.bind(myObject);

anotherFunction(); // "test"

but you could also use bind on a function expression.Think of function expressions as just another object.You can pass function expressions inside function parameters, you can add properties on them, and you can use call(), apply(), or bind() on them :

var myObject = {
    myString: "test"
}

var anotherFunction = function() {
    console.log(this.myString);
}.bind(myObject);

anotherFunction(); // "test"

#12

Okay I understand it now. Thanks :slight_smile:


#13

I’ve tried everything. It doesn’t seem to want to work. I tried declaring the getJSON seperately and then adding .call(quoteData). Also, I tried using .bind.

I understand the difference between bind and call, but I just can’t seem to make it work. Do you know how to make it work? Thanks again.


#14

Like this :

$(document).ready(function() {

  var quoteData = {
    init: function () {
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function(qData) {
        this.quoteString = qData.quote;
        this.quoteCharacter = qData.character;
        console.log(quoteData.quoteString); // prints the quote, this has become quoteData
      }.bind(quoteData) ); // <= added bind(quoteData).You could also write bind(this), as "this" still points to quoteData
    },
    quoteString: "",
    quoteCharacter: ""
  };

  quoteData.init();


});

another way would be to use scope :

$(document).ready(function() {

  var quoteData = {
    init: function () {
      var self = this; // <= added
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function(qData) {
        self.quoteString = qData.quote;
        self.quoteCharacter = qData.character;
        console.log(quoteData.quoteString);
      });
    },
    quoteString: "",
    quoteCharacter: ""
  };

  quoteData.init();

});

#15

I did that with the bind, but it doesnt seem to work. In the console quoteString and quoteCharacter are still "":
$(document).ready(function() {

    var quoteData = {
      init: function getQuoteData() {
        $.getJSON("https://got-quotes.herokuapp.com/quotes", function getJSON(qData){
          this.quoteString = qData.quote;
          this.quoteCharacter = qData.character;
        }.bind(quoteData));
      },
      quoteString: "",
      quoteCharacter: ""
    }
    quoteData.init();
    console.log("quote: " + quoteData.quoteString ); // quote: 
    console.log("character: " + quoteData.quoteCharacter ); // character: 
  });

#16

That is because of asynchronous code.

console.log() doesn’t wait for getJSON to return the data from the server, it executes immediately.But if you make console.log executes 2 seconds after (when the server is expected to have returned data):

$(document).ready(function() {

  var quoteData = {
    init: function () {
      var self = this;
      $.getJSON("https://got-quotes.herokuapp.com/quotes", function(qData) {
        self.quoteString = qData.quote;
        self.quoteCharacter = qData.character;
      });
    },
    quoteString: "",
    quoteCharacter: ""
  };

  quoteData.init();
  
  setTimeout(function(){
    console.log(quoteData.quoteString); // it works
  }, 2000);

});

Of course, I’m not telling you to make use of setTimeout in your code.I’ve used it to demonstrate what happens with asynchronous code.

Like bethqiang said, you can choose between putting your code inside the callback(like we’ve done so far), or use promises, but that’s another topic.


#17

I guess that ties into what @bethqiang was talking about then. I’ll have to do some more research into how I can change the code so I can make getJSON excecute the callback only when its received the data. Thanks so much for your help!