AJAX object Confusion

AJAX object Confusion
0

#1

I am now starting some of the api projects and I am wanting to first use pure JavaScript (without jQuery) to do them and then I will go back a second time using jQuery and other handy libraries. So I am stumped on something which is probably simple, but I have looked at it too long and can not see why it does not work. I want someone to point out the flaw in my thinking, but not just flat out tell me the solution. I still want to attempt to solve it myself. I just want a nudge in the right direction.

I have two slightly different versions trying to accomplish the same thing.

Version #1 (see below) works as I expected it would. It prints the JSON object contents to the console.
http://codepen.io/rmdawson/pen/GmJvZm

Version #2 (see below) prints undefined to the console.
http://codepen.io/rmdawson/pen/zwGPjm

Why does version #2 not have the same result?

Thanks in advance!


#2

In the 2nd example, you are console.logging the result of your loadJSON function. It’s asynchronous and doesn’t return anything. When the console.log line is called, the loadJSON function has returned, (but not the async request yet).

All functions in Javascript return a value. However, if you do not explicitly return a value using a return statement, Javascript functions return the value undefined. That’s why your second example shows undefined.

In order to console.log your JSON data, you must place the console.log statement inside a function which can see that data (according to scoping rules).

The first example works because you are console.logging inside of a callback function executed after the async function has returned and into which the data loaded by the async function is passed - it has access to the data.

This line:

callback(xobj.responseText)

is essentially:

actual( xobj.responseText )

As the variable callback in loadJSON points to the function actual.


#3

console.log(loadJSON('http://ip-api.com/json',actual)); prints the result from loadJSON, but the function loadJSON doesn’t return anything. Besides, you probably would want to return the data from the AJAX request which would be async and thus not work with console.log(loadJOSN), because it doesn’t return anything (even if you added something like return xobj.responseText) synchronously.

Note that if you made the actual function in v2 the same as in v1, you will see that the AJAX request is still successfull.

EDIT: Basically what @michealhall said…

EDIT 2: You might want to use the Fetch API instead of XMLHttpRequest. It is much easier (and prettier), but you will need a polyfill.


#4

Thanks to @michealhall and @BenGitter for your responses. The console.log stuff I was doing was just to make sure I could see what was going on.

Still not getting what I want. Below is another version with descriptions of what I think/want each function to do. See if you can tell me how to think about modifying my functions to accomplish the following. I ditched the callback function to see if it would do what I want

  1. on page load, run the init function
  2. init runs loadJSON with the ip-api.com url and assigns the result to const data and then print the object to the console.

What I expect is for both console.logs to print the same thing. I want the const data in init function to get the JSON object. How do I make this happen? Ultimately, I will be making to separate AJAX calls (one using the ip-api.com and then using longitude and latitude of that returned object, I will then make another AJAX call to get weather data from another url. I only want one function that gets the obj so I can make the 2 different calls to get 2 different results.

I am having trouble visualizing what is happening when my code executes.

 const loadJSON = (url,callback) => {   
   const xobj = new XMLHttpRequest();
   xobj.overrideMimeType("application/json");
   xobj.open('GET', url, true);
   xobj.onreadystatechange = () => {
     if (xobj.readyState == 4 && xobj.status == "200") {   
       const parsed = JSON.parse(xobj.responseText);
       console.log('inside loadJSON');
       console.log(parsed);
       return parsed;
     }
   };
   xobj.send(null);  
 }
 
// const getData = response => JSON.parse(response);
const init = () => {
  const data = loadJSON('http://ip-api.com/json');
  console.log('inside init');
  console.log(data);
};
window.addEventListener('load', init);

#5

The same exact thing applies though - regardless of whether or not you are using console.log.

loadJSON does not return a value - it cannot. It returns and execution of remaining code continues before data ever comes back from the server. That’s what makes the request “asynchronous”. When you try to assign the result of loadJSON to a variable outside of it, you get undefined, because that’s what loadJSON returns.

You need to use a callback to make use of the data returned (no way around it in this instance). Inside of that callback, you need to assign the returned data to a variable or as a property on an object which other functions can see outside of the scope of the request.

However, the gotcha is that those functions still will not be able to see the value until it is returned after the asynchronous request is completed. This means that they cannot run until after the request is finished, if they rely on the data.

So you either need to perform all handling that relies on the data inside the callback (the callback can call other functions, passing the data around as needed), switch to using promises (which is still largely the same pattern as a callback) or use an event based system whereby your callback triggers an event when the asynchronous request is completed and other components in your system listen for that event and take action when they receive it.

I suppose, if you are using a front-end framework, by getting data from the server and updating your data store in memory, they would take care of re-rendering components for you, but in all honesty - even though I author a fairly large project with tons of data and UI - I’ve never used a front end framework for anything, so I have no experience there.

I posted a code sample some time back here (this is one of the the most common questions on the forums) :

It won’t give anything away - it’s just a pattern I use. You’ll still be free to figure things out for yourself. :slight_smile:


#6

Thanks for the quick response @michealhall. I will take a look at the information you have given me and keep trying to figure it out.


#7

What is happening when my code executes?

I am going through each line in detail. Let’s start with:

const xobj = new XMLHttpRequest();

Here you used the XMLHttpRequest() constructor to create an XMLHttpRequest object; an API that provides client functionality for transferring data between a client and a server.

xobj.open('GET', url, true);

The XMLHttpRequest object have two primary types of XMLHttpRequest methods; Request & Response. Here we are using a Request method; .open(method, url, async).

The name .open() can be confusing as it might seem it Initiates the request. But it does not do that. Instead It sets / prepares the request method, request URL, and asynchronous flag.

xobj.onreadystatechange = ...

The .onreadystatechange is an event handler which is triggered EVERY time the readyState; a response method property, changes.

There are 5 values of readyState:

| Value | State            | Description                                                   |
|:------|------------------|---------------------------------------------------------------|
| 0     | UNSENT           | Client has been created. open() not called yet.               |
| 1     | OPENED           | open() has been called.                                       |
| 2     | HEADERS_RECEIVED | send() has been called, and headers and status are available. |
| 3     | LOADING          | Downloading; responseText holds partial data.                 |
| 4     | DONE             | The operation is complete.                                    |

if (xobj.readyState == 4 && xobj.status == "200")

As you might’ve guessed now, we are interested in xobj.readyState === 4; which means the operation is complete. Here you’re checking for the readyState Number 4 every time the onreadystatechange is called. But what about xobj.status == "200"?

The status is another response method that returns the HTTP status number.

200 is an “OK” HTTP status. It means the request has succeeded. so combining that with xobj.readyState == 4 You can be certain that your XHR request is all set and done.

const parsed = JSON.parse(xobj.responseText);

The information returned from this GET request is returned as text. So we used the JSON.parse to turn it into a usable JSON object.

xobj.send(null);

The send() method initiates the request; the real deal. Usually, when the argument is set to NULL it means this is a GET request. The argument is ignored if the request method is GET anyways.

Summary:

You create an XHR object. Sets the request (.open()) and Send it afterwards (.send(null)). Then whenever the onreadystatechange event is triggered, you check for (xobj.readyState == 4 && xobj.status == "200") and if they’re both true you do whatever you want with your request.

UPDATE: .overrideMimeType(mimetype)

This is used to force a stream to be treated and parsed as whatever MIME type you set (application/json).
It is used to prevent parsing errors. Read more…


So that’s pretty much it.
Here is the official specifications for XHR. It can be is pretty confusing, but it’s good to have.

Good luck.


#8

Well, I am not finished with the project, but I got something working. I did not look at your page (@michealhall) until I came up with my solution. I am not finished yet, because I still need to do the unit swap functionality and make it visually more appealing. I will now also experiment with Promises, to see if I can make my code better.

Thanks to everyone who replied with such detail. I learned something new today, which is always a good thing.

http://codepen.io/rmdawson/full/GmJvZm/