Handling an AJAX response in JavaScript (with or without jQuery)

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.

7 thoughts on “Handling an AJAX response in JavaScript (with or without jQuery)

  1. 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.

    function AJAX(querystring) {
    	if (window.XMLHttpRequest) {
    		var xmlhttp=new XMLHttpRequest();
    	} else {
    		var xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    	}
    	xmlhttp.onreadystatechange=function() {
    		if (xmlhttp.readyState==4 && xmlhttp.status==200) {
    			return xmlhttp.responseText;
    		}
    	}
    	xmlhttp.open("GET","/ajax.operator.php?" + querystring,true);
    	xmlhttp.send();
    }

  2. Joshua: Your code is returning the responseText from the handler of the onreadystatechange event, not from the AJAX 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:

        function loadSomething() {
            var queryString = "value=" + document.getElementById("foo").value;
            var response = AJAX(queryString);
    
            document.getElementById("output").innerHTML = response;
        }
    

    … you instead need to modify your AJAX function to accept a callback, which continues where you left off;

    function AJAX(querystring, callback) {
    	if (window.XMLHttpRequest) {
    		var xmlhttp=new XMLHttpRequest();
    	} else {
    		var xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    	}
    	xmlhttp.onreadystatechange=function() {
    		if (xmlhttp.readyState==4 && xmlhttp.status==200) {
    			callback(xmlhttp.responseText);
    		}
    	}
    	xmlhttp.open("GET","/ajax.operator.php?" + querystring,true);
    	xmlhttp.send();
    }

    … and then…

        function loadSomething() {
            var queryString = "value=" + document.getElementById("foo").value;
            
            AJAX(queryString, function (response) {
                document.getElementById("output").innerHTML = response;
            });
        }
    

    … Hope that helps :).

  3. 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

  4. 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;
    });
    }

  5. 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

Leave a Reply

Your email address will not be published. Required fields are marked *