Asynchronous XMLHttpRequests with XhrIo

Overview

JavaScript's XMLHttpRequest enables the responsive, persistent user interfaces that characterize AJAX applications. Web applications can use XMLHttpRequest to communicate with the server from JavaScript without reloading the page or blocking.

But while most modern browsers support XMLHttpRequest, different browsers have different implementations. To make sure your app has the same behavior for each browser, you have to do extra work.

You can smooth over these browser differences by using the Closure Library's XhrIo class. XhrIo also adds the ability to set timeouts, and handles all notification for both readyState changes and timeouts with a single set of informative event types.

This document first introduces XhrIo with a quick example, then describes in greater detail the process of sending a query, monitoring its status, and processing its results.

Quick Examples

There are two ways to send an asynchronous request with XhrIo: by calling the the goog.net.XhrIo.send() utility function, and by instantiating goog.net.XhrIo and calling the instance's send() method.

send() Utility Function Example

The Closure Library provides a simple utility function for the common use case of one-off requests. The following example illustrates its use.

[ xhr-quick.js ]

goog.require('goog.net.XhrIo');

/**
 * Retrieve JSON data using XhrIo's static send() method.
 *
 * @param {string} dataUrl The url to request.
 */
function getData(dataUrl) {
  log('Sending simple request for ['+ dataUrl + ']');
  goog.net.XhrIo.send(dataUrl, function(e) {
      var xhr = e.target;
      var obj = xhr.getResponseJson();
      log('Received Json data object with title property of "' +  
          obj['title'] + '"'); 
      alert(obj['content']);
  });
}

function log(msg) {
  document.getElementById('log').appendChild(document.createTextNode(msg));
  document.getElementById('log').appendChild(document.createElement('br'));
}

The call to goog.net.XhrIo.send() above passes two arguments:

  • The URL to request.
  • A function to call when the response is received. In the context of this callback function, the keyword this refers to an XhrIo instance holding the result. We use the getResponseJson() method of this instance to evaluate the response as JSON and retrieve the resulting JavaScript object.

In the example HTML page this function is registered as an onload handler:

<body onload="getData('xhrio_data.json')">

When the document loads, getData() sends an asynchronous network request for xhrio_data.json. Note that in this simple example the response is just a static file served by the web server; in a real web application the server might generate this response dynamically. In this case, the file we're requesting looks like this:

{
  "title": "My JSON Data",
  "content": "What's mine is yours."
}

This text consists of proper JavaScript syntax for an object literal (i.e., JavaScript Object Notation or JSON), so we retrieve the response as a JavaScript object using the getResponseJson() method. You can also retrieve and parse XML, or you can simply get the raw text of the response, as described in Retrieving Request Results.

send() Instance Method Example

You can also send a request by creating a new XhrIo instance, adding an event listener, and calling the send() method on the instance:

[ xhr-quick2.js ]

  goog.require('goog.net.XhrIo');

  var xhr = new goog.net.XhrIo();
  
  goog.events.listen(xhr, goog.net.EventType.COMPLETE, function(e) {
    obj = this.getResponseJson();
    log('Received Json data object with title property of "' +  
        obj['title'] + '"'); 
    alert(obj['content']);
  });
    
  function getData(dataUrl) {
    log('Sending simple request for ['+ dataUrl + ']');
    xhr.send(dataUrl);
  }

Sending and Monitoring a Request Using XhrIo

The examples above illustrate the two ways to send a request with XhrIo: the send() utility function and the send() instance method. These two approaches have advantages and disadvantages:

  • The send() utility function
    • Pros: simple
    • Cons: limited, inefficient
  • The send() instance method
    • Pros: flexible, allows reuse of XhrIo instances
    • Cons: takes more steps to set up

The following sections discuss each of these two approaches in greater detail.

Using XhrIo's send() Utility Function

The utility function XhrIo.send() provides a way to make an asynchronous request with a single call. It takes an optional callback parameter, and this callback is called once whenever the request has been resolved or has been determined to have failed for any reason (HTTP error, timeout, etc.). The complete signature for XhrIo.send() is:

goog.net.XhrIo.send(url, opt_callback, opt_method, opt_content,
         opt_headers, opt_timeoutInterval)

url

The URL to which you want to send the HTTP request. This can be a string or an instance of goog.Uri.

opt_callback

The function to be called when the request has been completed. The callback is passed an event object representing the request completion, and the target property of this event object points to the XhrIo object used to send the request.

opt_method

The HTTP method of the request (usually either "POST" or "GET"). Defaults to "GET".

opt_content

The data to be posted for POST requests.

opt_headers

A map of key-value pairs to be added to the HTTP request as headers. Either a plain JavaScript Object or a goog.structs.Map.

opt_timeoutInterval

The number of milliseconds after which an incomplete request will be aborted. A value of 0 prevents a timeout from being set, in which case the request is governed by the browser's timeout (which is usually 30 seconds).

XhrIo.send() sends its request by creating an XhrIo instance. It attaches an event listener to the XhrIo to listen for request completion. It also attaches a listener that automatically disposes of the XhrIo after the completion message has been dispatched. Note that XhrIo.send() creates a new XhrIo instance for every request.

When the request completes, the callback function is passed an event object representing the completion. The target property of this object refers to the XhrIo instance used to execute the request. You can use this target property to retrieve information from the XhrIo instance.

For example, if your callback parameter is called e, in your callback function you can call e.target.isSuccess(), e.target.getResponseText(), or any of the other instance methods of XhrIo. When you use the static send() method you don't have the opportunity to attach your own event handlers to its short-lived XhrIo instance. The opt_callback parameter is the only vehicle for notification available to you, and it is only called for 'complete' events (which will be fired for both successfully completed and aborted requests).

Using XhrIo's send() Instance Method

XhrIo's send() instance method provides a wider and more flexible interface than the utility function. Instead of a single callback function, this approach uses event listeners to get information about the status of the request.

To send a request using the send() instance method:

  1. Create or acquire an XhrIo instance.
  2. Attach event listeners to the instance to monitor the request and process its results.
  3. Optionally set a timeout interval.
  4. Send requests by calling the instance's send() method.

Creating or Acquiring an XhrIo Instance

The constructor XhrIo() does not take any parameters. You define a request by passing parameters to the instance's send() method rather than through constructor parameters.

Using goog.net.XhrIoPool

If you need to submit multiple requests simultaneously, consider using an instance of goog.net.XhrIoPool. XhrIoPool maintains a pool of XhrIo instances, supplying an instance on request if one is available (that is, if there is an instance in the pool that is not currently processing a request).

To complete signature of the XhrIoPool constructor is:

new goog.net.XhrIoPool(opt_headers, opt_minCount, opt_maxCount)
opt_headers
A map of HTTP header name/value pairs to add to every request sent with an XhrIo instance from the pool.
opt_minCount
The minimum number of XhrIo instances to keep in the pool at all times (defaults to 1).
opt_maxCount
The maximum number of XhrIo instances (defaults to 10).

To get an XhrIo instance from the pool, call

getObject(opt_callback, opt_priority)

where opt_callback is a callback function that is called when an XhrIo instance becomes available (with the instance passed as the parameter to the callback), and opt_priority is a number that determines which requests get fulfilled first if there is a shortage of instances in the pool (lower numbers indicating higher priority). If you omit opt_callback, an instance is returned if one is available, and undefined is returned if no instance is available.

When you are done using an XhrIo instance from a pool, call the pool's releaseObject(obj) method (where obj is the XhrIo instance) to return the instance to the pool so that it can be reused.

In addition to the queuing facilities provided by XhrIoPool, the Closure Library also provides more sophisticated connection management through goog.net.XhrManager. XhrManager uses an XhrIoPool instance, but enhances the basic pool with automatic retries and aggregate event handling. Its use is beyond the scope of this document, but you might find it helpful if you need advanced connection management capabilities.

Handling Request Events

Listening for Request Events

Attach event listeners to the XhrIo instance to monitor request status and process request results. XhrIo listens for the native readystatechange events, performs the required lookup and interpretation of the readyState, and then dispatches its own events. XhrIo dispatches the following types of events:

Event type constantWhen event is dispatched
goog.net.EventType.READY_STATE_CHANGE Dispatched on any change to the ready state. In other words, fired in response to any native browser readystatechange event.
goog.net.EventType.COMPLETE Dispatched when the request has been resolved, whether successfully or not.
goog.net.EventType.SUCCESS Dispatched after the COMPLETE event fires if the request completed successfully.
goog.net.EventType.ERROR Dispatched after the COMPLETE event is dispatched if the request did not complete successfully.
goog.net.EventType.READY Dispatched after the XhrIo instance has been reset following the completion of the previous request. Indicates that the XhrIo instance is ready for another request.
goog.net.EventType.TIMEOUT Dispatched if the request does not complete before the timeout interval has elapsed (see Setting a Timeout Interval). After the TIMEOUT event is dispatched, the request is aborted, triggering the dispatch of a COMPLETE event and then an ABORT event
goog.net.EventType.ABORT Dispatched by the abort method of XhrIo. Before dispatching an ABORT event, the abort method:
  1. calls abort() on the XhrIo instance's XMLHttpRequest,
  2. records an error code in the XhrIo instance, and
  3. dispatches a COMPLETE event.

In the quick example, for example, an event handler is attached to the XhrIo instance xhr with the following code:

goog.events.listen(xhr, goog.net.EventType.COMPLETE, function(e) {
  var xhr = e.target;
  var obj = xhr.getResponseJson();
  log('Received Json data object with title property of "' +  
      obj['title'] + '"'); 
  alert(obj['content']);
});
Checking the Request's Status

XhrIo provides several methods for checking the status of a request and getting its results. You may find these methods useful in your event listener functions:

MethodReturns
isActive()true if send() has been called on the XhrIo instance and the request triggered by this call has not yet errored, been aborted, or completed successfully.
isComplete()true if the underlying XMLHttpRequest has a readyState of 4.
isSuccess()true if the request has completed without an error or timeout.
getReadyState()The readyState of the underlying XMLHttpRequest.
getStatus()The HTTP status code sent by the server (200 for success, 404 if the URL wasn't found, etc.) if the server has started sending its response. If the server has not started responding, returns -1.
getStatusText()The status text sent by the server ("OK", "Not found", etc.) if the server has started sending its response. If the server has not started responding, returns an empty string.
getLastUriThe last Uri that was requested.
getLastErrorCode() A goog.net.ErrorCode value indicating the outcome of the latest request. goog.net.ErrorCode (defined in closure/net/errorcode.js) defines constants used to record network error types, including the following types used by XhrIo:
  • goog.net.ErrorCode.NO_ERROR
  • goog.net.ErrorCode.EXCEPTION
  • goog.net.ErrorCode.HTTP_ERROR
  • goog.net.ErrorCode.ABORT
  • goog.net.ErrorCode.TIMEOUT
getLastError()Text indicating the source of the error produced by the latest request, or an empty string if there was no error.
getResponseHeader(key)The value of the HTTP response header indicated by key.

Retrieving Request Results

XhrIo provides three methods for retrieving the results of a request. If you need to get information back from the server, then you will have to call one of these methods. These methods differ from each other only with respect to the way in which the response text is processed and returned: as raw text, as a JavaScript object obtained by evaluating a JavaScript Object Notation (JSON) response, or as Document object obtained by parsing an XML response.

MethodReturns
getResponseText()The raw text of the response. The server's response can always be accessed with this method, no matter how it is encoded.
getResponseJson()Evaluates the response as JavaScript and returns the resulting object. The server's response must be JSON-encoded in order for this method to work. That is, the response must be a JavaScript object expression. Note that getResponseJson() uses a safe JSON parse (goog.json.parse()), and might therefore be slow for large amounts of data. If you trust the data you can instead retrieve the raw text response with getResponseText() and pass it to goog.json.unsafeParse() to get a JavaScript object.
getResponseXml()Returns a Document object produced by parsing the response text as XML. The server's response must have a content type of "text/xml" in order for this to work. The getResponseXml() method returns the value of the responseXML property of the underlying XMLHttpResponse object.

Setting a Timeout Interval

The timeout interval is a property of the XhrIo instance itself, and persists across requests. Set the timeout using the goog.net.XhrIo.prototype.setTimeoutInterval() method, which takes a parameter indicating the number of milliseconds to wait before aborting a request. XhrIo interprets a timeout of 0 milliseconds as indicating no timeout at all. If you do not set a timeout for the XhrIo instance (or if you set a timeout of 0), requests default to the browser's timeout, which is usually 30 seconds.

Sending the Request

Once you have created an XhrIo instance and attached event listeners to handle the request results, initiate the request by calling the instance's send() method:

send(url, opt_method, opt_content, opt_headers)

url
indicates the uri to which you want to send the HTTP request.
opt_method
indicates the HTTP method of the request (usually either "POST" or "GET"). Defaults to "GET".
opt_content
contains the data to be posted for POST requests.
opt_headerscontains key-value pairs to be added to the HTTP request as headers. Either a plain JavaScript Object or a goog.structs.Map.

As discussed above, the send() method does not take a parameter indicating the timeout period to use for the request. By default an XhrIo instance has no timeout, but if you want to set a timeout call setTimeoutInterval(interval) before calling send(), where interval is the timeout interval in milliseconds.