Recent Weblogs

Links I like

The Lonely Request

In the programming model of a client receiving data from an HTTP source is the always present request and response model. This stays true when employing the technique of Ajax as well but just in typically smaller packages. There are instances where the client simply won't receive a response. In a typical HTTP request the browser will timeout and the user will be notified of the error and can take action to try again or go elsewhere. When using an XHR, the heart and soul of Ajax, the request can linger indefinitey without proper precautions. The XHR does have an abort method but has no internal timeout functionality. In this article we're going to take a look at the Ajax.Request class in prototype.js library and look at what would be necessary to implement timeout functionality via refactoring the Ajax.Request class.

The Timeout Model

The idea would be to allow another configuration property to set the timeout in a number of seconds, and debatebly another callback specifically for onTimeout. This is where the clarity of the functionality begins to cloud. If the request has timed out then has it not in fact failed, so should onTimeout and onFailure events both be fired? The alternative being that if the timeout duration has been exceeded the transport should be aborted and the onFailure method should be executed. This difference in behavior seems subtle but I believe it should be decided wisely. I am in favor of simply executing the onFailure method and setting a property of the object to indicate that it has timed out. in that approach we're able to maintain explicit functionality for the timeout case while avoiding bloat in the options argument.

Failure with Timeout

This approach would fire both events. The onFailure and onTimeout, I believe this approach could become sticky as they're very similar cases. If both execute there could be a lot of redunandant action as your onFailure method tries to clean up after a failed XHR. While the onTimeout will be doing more or less the same thing with perhaps a variation of notification to the user.

Just a Failure

I believe this approach to be the cleanest, to simply execute the onFailure event to handle the network failure and clean up the application as necessary. Although it is the minimalist approach it could quickly bloat when implementing specialized procedures for the onTimeout case.

Timeout Alone

Keeping it clean yet giving the timeout case a designated event. This certainly keeps things clean and specific but could be a bit of a nuisance when trying to re-use code from you onFailure handler.

One More Method

I also think that there should be a convienence method for the Ajax.Response class. It is an ecapsulation class that wraps the Ajax.Request and is sent to the event handling methods such as onComplete etc. This class could do well to have a "hasTimedOut" method which would allow for a handy method at the top level for programmers using prototype that havn't navigated the depths of the Ajax mechanisms.

  • Prototype.js Learn more about the fantastic Javascript library that this enhancement is based upon.
  • View the demo I've set up a basic demo flexing this functionality and as of now it works in IE7 and FF2.
  • Research the XHR Timeout this is no new idea, check out what others have thought of this functionality.

The Code

Differences varying from the prototype 1.6 release are noted in red text.

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,
  timeout: false,
  initialize: function($super, url, options) {
    this.transport = Ajax.getTransport();	

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);, this.url,

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
      if(this.options.timeoutDelay && this.options.onTimeout)
    catch (e) {

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete) && !this.timeout)

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);

  success: function() {
    var status = this.getStatus();
    return (status >= 200 && status < 300);

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  clearTimeout : function(){
  startTimer : function(sec){
     this.timer = setTimeout(this.handleTimeout.bind(this), (sec*1000));  
  handleTimeout : function(){
        this.timeout = true;
        this.options.onTimeout(new Ajax.Response(this));
      catch (e) {
  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
	if (state == 'Complete') {
      try {
		this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);		 
      } catch (e) {

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && this.isSameOrigin() && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {

    if (state == 'Complete') {      
	  // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } catch (e) { return null }

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);


Warning: mysql_connect() [function.mysql-connect]: Access denied for user 'mfoster'@'' (using password: YES) in /home/content/m/a/t/mattfoster01/html/includes/php_common/lib/classes/abstract.mySQL.php on line 15

Fatal error: Uncaught exception 'Exception' with message 'Failed to connect to mySQL server in method : mySQLBase::connect' in /home/content/m/a/t/mattfoster01/html/includes/php_common/lib/classes/abstract.mySQL.php:16 Stack trace: #0 /home/content/m/a/t/mattfoster01/html/includes/template/comment.php(29): mySQLBase->connect('', 'mfoster', 'havef8th') #1 /home/content/m/a/t/mattfoster01/html/blog/2008/07/prototype-ajax-request-timeout.php(275): include_once('/home/content/m...') #2 {main} thrown in /home/content/m/a/t/mattfoster01/html/includes/php_common/lib/classes/abstract.mySQL.php on line 16