Theory
How do you wait for something asynchronous?
Last lesson's runner departed with send(), and your code moved on immediately: that was the point. But then... how does your script ever learn the answer ARRIVED?
You cannot loop and check (that would freeze the page: the exact disease we are curing). The runner must ring a bell when there is news.
The XMLHttpRequest object is built around that bell, a progress counter, and 2 pockets for the answer: 4 properties that between them run every AJAX call ever made.
Theory
The bell and the counter
onreadystatechange holds the function you want called whenever the request's state changes: assign it BEFORE sending. It is not called once; it fires at every stage of the journey.
readyState is the stage number, 0 through 4. The request walks up the scale as it progresses, and each step rings your handler. The one you almost always await is 4: finished, response ready.
Bell plus counter is the asynchronous answer to "how do I wait?": you do not wait: you get called.
At a glance
readyState 0 to 4 (the exam table: learn it verbatim)
| Value | State | Meaning |
|---|---|---|
| 0 | Request not initialized | Object created; open() not yet called |
| 1 | Server connection established | open() has been called |
| 2 | Request received | send() called; response headers arrived |
| 3 | Processing request | Response body downloading |
| 4 | Request finished, response ready | The answer is fully in hand |
Theory
The two pockets, and the verdict
When readyState hits 4, the answer sits in one of 2 properties:
- responseText: the response as a plain string: THE pocket for JSON (
JSON.parse(xhr.responseText)) and any text - responseXML: the response as a parsed XML document, ready for DOM-style access (
getElementsByTagName): meaningful only when the server actually sent XML
And one more property renders the verdict: status, the HTTP code: 200 means OK; 404 means not found. Finished (readyState 4) and successful (status 200) are different facts: check both.
Practical
Watching the journey, state by state (complete page)
<!DOCTYPE html>
<html>
<head>
<script>
function fetchSeats() {
var xhr = new XMLHttpRequest(); // readyState is 0 here
xhr.onreadystatechange = function() { // the bell: fires per stage
document.getElementById("log").innerHTML +=
"readyState: " + xhr.readyState + "<br>";
if (xhr.readyState == 4 && xhr.status == 200) {
var data = JSON.parse(xhr.responseText); // the string pocket
document.getElementById("seats").innerHTML = data.seats;
}
if (xhr.readyState == 4 && xhr.status == 404) {
document.getElementById("seats").innerHTML = "file missing!";
}
};
xhr.open("GET", "seats.json", true); // readyState becomes 1
xhr.send(); // then 2, 3, 4 as it runs
}
</script>
</head>
<body>
<p>Seats: <span id="seats">?</span></p>
<div id="log"></div>
<button onclick="fetchSeats()">Fetch</button>
</body>
</html>
This example runs in Gri-Learn on the web, where you can edit it and see the output.
Quiz
An AJAX handler updates the page inside if (xhr.readyState == 4) with no other condition. The server replies 404 because seats.json was renamed. What does the user see?
- Nothing: readyState never reaches 4 on an error
- The page updates with the 404 error page's text poured in as if it were data
- The browser retries the request automatically
- JSON.parse waits until a correct file appears
Show the answer
The page updates with the 404 error page's text poured in as if it were data
readyState measures COMPLETION, not success: a 404 response is a completed request, so state 4 arrives carrying the server's error page as responseText, and unguarded code parses or displays garbage (JSON.parse throwing on '<html>...' is this bug's loudest symptom). That is why the canonical guard is readyState == 4 AND status == 200: finished AND successful. Option A is the exact misbelief being tested. Options C and D describe mercies that do not exist: no retries, no waiting: the response you got is the response you handle.
Think first
How many times does the bell ring?
Click Fetch in the listing and read its log div. Before tapping: how many lines appear for one successful request, showing which numbers, and why does the handler need an if at all?
Show the answer
Typically 4 lines: 1, 2, 3, 4: onreadystatechange fires at every transition after open() (state 1 as open executes, then 2, 3 and 4 as the response arrives; 0 was the state at creation, before any change fires the bell). The if exists precisely because of this multiplicity: your update-the-page code must run at stage 4 ONLY: without the guard it would attempt to read an answer that has not arrived, 3 times per request. Bell rings often; you act once.
Watch out
Property mix-ups that cost marks
responseText vs responseXML: text is a string for JSON.parse and display; responseXML is a pre-parsed XML DOCUMENT: calling JSON.parse on responseXML, or getElementsByTagName on responseText, are both category errors.
Assigning the handler AFTER send(): on a fast (cached) response, early states can fire before your bell is installed: always wire onreadystatechange before send.
Writing the table loosely: "2 means request received, 3 means processing" must not swap: exams grade the exact wording.
Theory
The pattern to memorise as one unit
Every raw AJAX call you will ever write is the same 5-line skeleton: create the object, assign onreadystatechange, guard with 4-and-200, open, send. Learn today's listing until you can reproduce it with your eyes closed: exam AJAX questions are this skeleton with the URL changed. Two verbs remain unexplained in it: what open() and send() precisely do, plus the third method for headers: next lesson finishes the object.
Summary
Key takeaways
- onreadystatechange holds your handler; it fires at EVERY state change, so guard inside it.
- readyState: 0 not initialized, 1 connection established, 2 request received, 3 processing, 4 finished and ready: learn the table verbatim.
- responseText = the answer as a string (JSON's pocket); responseXML = a parsed XML document.
- status is the HTTP verdict: 200 OK, 404 not found.
- The canonical guard: if (readyState == 4 && status == 200): finished AND successful.
- Wire the handler before send(); act only at stage 4.
- Memory hook: the bell rings 4 times; open the door on the fourth, and check who it is.