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) {
    $super(options);
    this.transport = Ajax.getTransport();	
    this.request(url);
  },

  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.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

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

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

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

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

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

  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];
      else
        $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(){
     clearTimeout(this.timer);
  },
  startTimer : function(sec){
     this.timer = setTimeout(this.handleTimeout.bind(this), (sec*1000));  
  },
  handleTimeout : function(){
      try{
        this.timeout = true;
        this.transport.abort();
        this.options.onTimeout(new Ajax.Response(this));
      }
      catch (e) {
        this.dispatchException(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) {
        this.dispatchException(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)))
        this.evalResponse();
    }

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

    if (state == 'Complete') {      
      this.clearTimeout();
	  // 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) {
      this.dispatchException(e);
    }
  },

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

Comments

August 07, 2008Tobie Langel

You'd want to either throw a specific exception (not the one caused by abort in some browsers) or have a dedicated callback.

August 26, 2008Tibo

What is going on if the response (onSuccess) arrive after the timeout event fired : can we disable the onSucces function when the timeout function is fired ?

September 18, 2008Ücretsiz driver indirin

Thanks

December 22, 2008nostalji filmler


Thank you very much for this information.
Good post thanks for sharing.
I like this site ;)

December 22, 2008Gazeteler

Good post thanks for sharing.

January 04, 2009Nick

This should be implemented.

January 16, 2009airbag tamiri

thanks

January 20, 2009netlog

thnksss

January 20, 2009almanya chat

thanksyou!^%%

January 26, 2009Grace

An Ajax framework is a framework that helps to develop web applications that use Ajax, a collection of technologies used to build dynamic web pages on the client side. Data is read from the server or sent to the server by JavaScript requests.

April 08, 2009Mike G

respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
if (state == 'Complete') {
change to
espondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
if (state == 'Complete' && this.timeout == false) {

September 24, 2009Michael

Hello friend,

Welcome to our company website: http://www.jordans-sneaker.com is a professional website of <a href="http://www.jordans-sneaker.com">Brand Shoes and Clothing</a> wholesale.
There are many new model of the <a href="http://www.jordans-sneaker.com">Brand Shoes and Clothing</a>, pls choose the products that you need from the website. All they are high quality,good price,All model,All color,All size. The more you order,the price is cheaper. If you have any question, pls don’t hesitate to let us know. We sincerely hope we will be a best supplier for you.
Waiting for your kind reply!
Website: http://www.jordans-sneaker.com
Email: jordans-sneaker@hotmail.com

September 24, 2009maplestory mesos

http://www.maplestory4mesos.com is a Specialized MapleStory Mesos store,we sell Maple Story Mesos,you can buy Safe Maple Story Mesos,Cheap Maple Story Mesos from us."

August 07, 2011business loans

If you are willing to buy real estate, you would have to get the mortgage loans. Furthermore, my mother usually uses a sba loan, which occurs to be really rapid.

August 31, 2011personal loans

I had got a dream to begin my commerce, nevertheless I did not have enough amount of money to do this. Thank God my mate said to use the loans. Thus I took the credit loan and made real my desire.

April 17, 2012blog posting services

Do you guess that is extra expensive to use blog posting service? Nevertheless, that can be more expensive if you try to make your articles submission work yourself!

October 17, 2012loan

Do you know that this is high time to get the home loans goodfinance-blog.com, which can help you.

February 01, 2013university writing

It’s important to pass all the ezaminations during the study at colledge. When students don’t have enough time for that, the can simply receive essay custom written by proficient writers.Get in touch with the PrimeWritings company. The top solution for gaining high marks is to use the PrimeWritings.company’s nice essay writing services.

March 27, 2013college papers

Buy essays online "essaysservice.com" in case you aren't confident of your writing talents and desire professional academic writer to compose a paper for you. Thankfully, there are various agencies that are eager to provide their essay services to the students who long to get pre-written essays.

June 12, 2013Agnes

You can always learn something new everyday. Impressive

August 19, 2013visit website

I know very well for internet accessibility to condut your content with totall transfer them.I think hyper text transfer protocol to allot a website when you taking any site hosting then you accessible for this.Thanks a lot & please to continue your enjoyable work.

Name
Site
Comment
  CAPTCHA Image
Reload Captcha Image
Captcha