The Problem & the solution
One of the most common questions asked on StackOverflow tends to revolve around AJAX, and the inability to immediately utilize the response of an AJAX request as follows;
var response = '';
var xhr = new XMLHttpRequest(); // Usual mix-and-matching for x-browser omitted for brevity
xhr.onreadystatechange = function () {
response = this.responseText;
}
xhr.open('GET', '/ajax.php', true);
xhr.send(null);
// Try and use 'response' or 'xhr.responseText' here (note: this will not work!)
Or equally in jQuery:
var response = '';
var xhr = jQuery.ajax('/ajax.php', function (result) {
response = result;
});
// try and use response here (note: this will not work!)
So common is this question on StackOverflow, that it features in the JavaScript tag description. The answer to each of these questions always advises the developer to use a callback to handle the response. We’ll look at why a callback is required shortly, but for those of you in a rush, I will first show the solution;
var xhr = new XMLHttpRequest(); // Usual mix-and-matching for x-browser omitted for brevity
// no need to declare 'response' here
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var response = this.responseText;
// use response in here.
}
}
xhr.open('GET', '/ajax.php', true);
xhr.send(null);
And the jQuery equivalent;
jQuery.get('/ajax.php', function (response) {
// use response here; jQuery passes it as the first parameter
});
The explanation
A callback is required because an AJAX, as the name Asynchronous JavaScript And XML suggests, is asynchronous. When you initiate a AJAX request using either the native XMLHttpRequest method or via jQuery, the HTTP request is sent, but the JavaScript engine does not wait for a response. Instead, the flow of execution continues. To enable us to monitor the progress of the HTTP request, we are allowed to provide callbacks which are executed when the state of the HTTP request changes. A callback can be provided to a native XMLHttpRequest object via the onreadystatechange
attribute;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
// Code inside here is executed each time the progress of the HTTP request advances.
// The current state can be retrieved via `this.readyState`, which returns a value ranging
// from 0 to 4 (inclusive).
if (this.readyState == 4) { // If the HTTP request has completed
if (this.status == 200) { // If the HTTP response code is 200 (e.g. successful)
var response = this.responseText; // Retrieve the response text
};
};
};
xhr.open('GET', '/ajax.php', 'true');
xhr.send(null);
When using jQuery, callbacks are specified differently depending on which AJAX method you used; methods such as jQuery.get
, jQuery.post
, jQuery.getJSON
accept only a success callback by providing a function as a parameter. jQuery.ajax
however, permits multiple, separate callbacks to be specified as name:value
pairs in the object passed in the last parameter.
jQuery.get('/ajax.php', function (response) {
// Handle success here
});
jQuery.ajax('/ajax.php', {
beforeSend: function () { /* AJAX request is about to be sent */ },
complete: function () { /* AJAX request has completed */},
success: function () { /* AJAX request has completed successfully */},
error: function () { /* AJAX request has completed with errors */}
})
The introduction of callbacks leaves the flow of execution looking something like this;
var xhr = new XMLHttpRequest(); // Usual mix-and-matching for x-browser omitted for brevity
// Point 1
xhr.onreadystatechange = function () {
// Point 4
if (this.readyState == 4) {
// Point 5
if (this.status == 200) {
var response = this.responseText;
// Point 6
}
}
}
// Point 2
xhr.open('GET', '/ajax.php', true);
xhr.send(null);
// Point 3
Point 1, 2 and 3 will be executed once in ascending order immediately. The execution of point 4 will then wait until some time later when the state of the HTTP request progresses. Point 4 will be reached and executed multiple times until the HTTP request reaches the completion state (readyState == 4
), upon which the flow of execution will reach Point 5. Point 6 will be executed if the HTTP request completed successfully. The same flow of execution can be witnessed in the following jQuery code;
// Point 1
jQuery.get('/ajax.php', function (result) {
// Point 6
});
// Point 3
It is a common pattern to show some sort of progress indicator at Point 3; usually something as simple as a loading spinner, to indicate that an asynchronous HTTP request is in progress. It would be ideal to provide updates to the status at Point 4, however the values observed through readyState
are not useful enough for this. In point 5, the HTTP request is finished, so the loading spinner can be removed, and either a success or error message can be shown, depending on whether the status
is a successful HTTP error code.
This is a very nice Blog Post indeed, but how do I make a function that is gonna return the result using “return”? It seems that it doesn’t work.
Joshua: Your code is returning the
responseText
from the handler of theonreadystatechange
event, not from theAJAX
function. Because AJAX is asynchronous, you have to pause your function; run half of it before the AJAX request, then hold off executing the second half until the AJAX call has completed.E.g, where you might previously have had:
… you instead need to modify your
AJAX
function to accept acallback
, which continues where you left off;… and then…
… Hope that helps :).
Matt, you are Heaven sent. Thanks a million.
Hey I really confuse get to know this issue (readystatechange handler). can you upload example code for me?
I want to make a chart, use chart.js + $.ajax() and I had trouble when I switch datasource to static source.
uncaught typeError: undefined is not a function
$.ajax.success
b
x.onreadystatechange
please help me to solved this issue, thanks
Thank you matt. That was a great help.
Hello. Thank you very much for explaining this.
I have a page with about 20 links that retrieve documents using your code. When I click one of the links, I get my results, but the resulting document is returned underneath the page with all the links. Is there any way to eliminate the calling links page and just display the results?
Thank you
function AJAX(docurl, callback) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
callback(xmlhttp.responseText);
}
}
xmlhttp.open(“GET”,docurl,true);
xmlhttp.send();
}
function loadDoc() {
var docurl = document.forms[0].docurl.value;
AJAX(docurl, function (response) {
document.getElementById(“demo”).innerHTML = response;
});
}
Hi David,
Without seeing your HTML, it’d would seem you are not clearing the element containing your links. Where you are doing
document.getElementById(“demo”).innerHTML = response;
, you should add another line to set the “innerHTML” for the element containing your links to an empty string. Alternatively, you could set the “style.display” to “none”, to hide it.If this doesn’t solve your problem, I recommend you posting a question containing a MCVE on Stack Overflow, and pinging me a link to it to take a look.
Cheers,
Matt