AJAX States
Learning outcomes:
- What are states of a request
- The
readyState
property - The
onreadystatechange
event
A quick recap
In the previous chapter we saw how the whole mechanism of AJAX works and that how is a basic request composed using the open()
and send()
methods of the XMLHttpRequest()
object.
open()
was used to initialise the request whereas send()
served the purpose of literally sending it to the desired location. In the discussion, however, we abstracted away certain details, specifically in the third, fourth and fifth steps.
From the point of dispatch of the request upto the point the server processes it and sends back information along with the necessary HTTP headers, the AJAX code at the client-end needs to literally track the state of the request and then the status of the data once returned by the server. This makes sure we are taking into account potential errors and problems creeping into our applications.
Didn't understand a word? Let's discuss it.
What is a state?
Say you set up an AJAX request by instantiating the XMLHttpRequest()
constructor and call open()
a while later. After calling the open()
method you call send()
, and then wait for a response to come back from the server. The response partially comes back with some headers, and then after a while with the actual data you wished for. Eventually all the data arrives at your end and the request completes.
These are the different states an AJAX request can be in, starting from the point it is created using the XMLHttpRequest()
constructor.
For example the request can be uninitialised, in the process of loading, loaded successfully and so on.
The significance of these states is that we can use them to track a request and see when does it reach completion, and hence the point where we can safely extract data out of it. Without states we could simply not know when a request ends - which is essential for a smooth and error-free workflow.
So how many states does AJAX requests have and how do the states actually look? Are they numbers, booleans, objects or some strange codes?
Let's discuss all this next.
Different states of a request
At the basic level, an AJAX request can take on one of the following 5 different states:
- After an
XMLHttpRequest()
object is instantiated and before theopen()
method is called, the state of the AJAX request stands as 0. This implies the request as uninitialised - since initialisation is done usingopen()
which is not called as of yet. - Once
open()
is called, and while thesend()
method is not, the state of the request changes to 1 - signifying it as initialised. - When
send()
is invoked and the headers of the response have been received the state changes to 2, implying it as interactive. - Now after the headers have been received successfully, obviously the system then waits for the actual content to start flowing in. As soon as it starts to flow in, the state changes to 3, which means that the request is now loading. Usually this happens pretty quickly.
- Finally, when all of the content arrives successfully at the client-end, the state changes to its last value - 4 - meaning that it has been loaded.
The state 4 indicates that our request has been successfully fulfilled and it is now that we can sensibly retrieve the data of the response. Retrieval of data before this point is meaningless since the request is still on its way to completion and hence still receving the data of the response.
Usually in an AJAX application, we are specifically interested in monitoring for this state change since it directly relates to being able to extract out the data from the response.
In short, every interesting thing that happens on AJAX requests, in effect, happens at state 4.
However, take special note of the fact that this state doesn't always mean that everything went good on the backend. It simply means that we have a response ready for our request - the response itself can even be an error message!
So these are the five states of any AJAX request from the beginning to the very end. Now a question arises that is there any way we can view these states for an AJAX request in JavaScript?
The answer is simple - yes!
The readyState
property
The readyState
property of the XMLHttpRequest()
object, holds any one of the numbers from 0 to 4 depending on the state of the request at the instance of its call.
Following are some instances of readyState
called at different places in the simple AJAX code we wrote in the previous AJAX Mechanism chapter:
var xhr = new XMLHttpRequest();
console.log(xhr.readyState); // 0
// readyState is 0 since the request is uninitialised
xhr.open("GET", "ajax.txt", true);
console.log(xhr.readyState); // 1
// readyState is 1 since now the request is initialised
As you can see from the logs above, and in accordance to what we said earlier, as soon as the XMLHttpRequest()
object is instantiated (and open()
is not called), its readyState
property gets set to 0
- which means "uninitialised". This can be verified by line 3.
Moving on, once open()
is called and the request initialisations are made (in line 6), its readyState
changes to 1
- implying "initialised". This can be verified by line 8.
Now at this point there is one thing worth mentioning, relating to the code above: we only made logs for states 0 and 1 because only they can be logged directly as shown above, unlike the states 2, 3 and 4. But why?
The way the states 0 and 1 work enables us to track them using direct logs as we did above. However for the other three states we can't do this because we don't know exactly when one will change to the other.
Following is an illustration of this problem to help you better understand it. Take note of the last three logs in the code, since the rest is essentially the same as the code shown above.
var xhr = new XMLHttpRequest();
console.log(xhr.readyState); // 0
xhr.open("GET", "ajax.txt", true);
console.log(xhr.readyState); // 1
xhr.send();
console.log(xhr.readyState); // 1
console.log(xhr.readyState); // 1
console.log(xhr.readyState); // 1
If you look closely here, you'll find certain strange things.
Firstly you might think that the three logs starting from line 8 output 1 because the response hasn't yet been received, but this is not the case at all. Instead what is happening is something really interesting and one most developers won't be aware of at all!
Let's see what this means.
How send()
works internally?
The interpreter comes across the send()
method in line 7 and likewise invokes it. This gets the internal browser engine to dispatch the desired request and consequently exit the method. The method completes at this point and the interpreter continues on with the next instruction.
Meanwhile the request initiated by the browser in the background completes and is therefore put in the task queue. It waits here until the JavaScript call stack gets empty. The stack empties only once the entire script finishes executing. Thus any further processing of the request, after it has been received and waiting in the queue, is delayed until the completion of the ongoing script.
This is the way JavaScript handles all asynchronous operations and events. They line up in the task queue, one after another, and execute only once the main script gets executed to its entirety.
Coming back to the topic, at least now we know the reason to why all the three logs above, after send()
, were equal to 1 - simply because the request was waiting in the queue, or not yet completed, until all the logs were made. Problem solved!
Now if you didn't understand all of this, or even a certain segment, don't worry; this concept is truly complicated and requires a bit of rock solid console experimenting before it can start to make sense.
However, even if all these sequence of operations didn't happen, and assuming an ideal mechanism of send()
where the request is processed exactly when it completes, rather than until the script's completion; no one will ever track states 2 to 4 using direct logs. It just doesn't make sense!
Hence, where all this long conversation converges and what is the whole point of considering states of a request is the idea of events.
Events to the rescue
Events are the ultimate solution to tracking state changes of an AJAX request. Each time the state changes, i.e the value of readyState
changes, an event is fired. This event can be assigned a listener to respond to the change.
It is named onreadystatechange
. Its name is quite self-explanatory to understand its purpose even if you knew nothing about it at all - it fires "on readyState
's change".
Anyways moving on, let's now use this event to handle state changes in our previous code and log readyState
inside the handler function, all more neatly and meaningfully.
var xhr = new XMLHttpRequest();
console.log(xhr.readyState); // 0
xhr.onreadystatechange = function() {
// whenever readyState changes, execute this code
console.log(this.readyState);
}
xhr.open("GET", "ajax.txt", true);
xhr.send();
Execute this and you'll find all the five states sequentially logged in the console.
Detailing one thing about the code above, if you notice one thing you'll see that the log for state 0 is made outside the handler of onreadystatechange
. The reason for doing so is because the event only fires for state changes i.e when it changes from 0 to 1, 1 to 2 and so on.
0 is the default state of a request as it gets instantiated - it doesn't come from a state change. Think on it and you'll soon realize the fact!
Spread the word
Think that the content was awesome? Share it with your friends!
Join the community
Can't understand something related to the content? Get help from the community.