Click here to Skip to main content
6,822,123 members and growing! (17,721 online)
Email Password   helpLost your password?
Web Development » Ajax and Atlas » General     Advanced License: The Code Project Open License (CPOL)

Internet Explorer Will Be Stuck When Requests Too Much? Fake the XHR!

By Jeffrey Zhao

Internet Explorer will be stuck when too many connections have been set up in the page at the same time. Let's build a fake XHR object to solve the bug for Internet Explorer browser.
Javascript, XML, Windows, Visual-Studio, Ajax, Dev
Revision:2 (See All)
Posted:21 Jun 2007
Views:15,405
Bookmarked:19 times
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
3 votes for this article.
Popularity: 1.23 Rating: 2.57 out of 5
1 vote, 33.3%
1

2

3
1 vote, 33.3%
4
1 vote, 33.3%
5

Introduction

Months ago, a friend of mine who is also a consultant and trainer, told me that one of his customers met with a problem. Internet Explorer will be stuck when too many connections have been set up in the page at the same time. The problem is becoming more and more popular since AJAX technology has been widely used these days. When an AJAX application is composed of smaller ones - that we call "mash up" - the problem will be likely to occur.

It's a bug in Internet Explorer. When you make a lot of AJAX calls, the browser keeps all the requests in a queue and executes two at a time. So, if you click on something to try to navigate to another page, the browser has to wait for running calls to complete before it can take another one. The bug is quite serious in Internet Explorer 6 and unfortunately, it still exists in Internet Explorer 7.

Manage the Requests Programmatically

The solution is simple. We should maintain the queue ourselves and send requests to the browser's queue from our queue one by one. Thus I wrote a queue to manage the requests. It's really a piece of cake:

if (!window.Global)
{
    window.Global = new Object();
}

Global._ConnectionManager = function()
{
    this._requestDelegateQueue = new Array();    
    this._requestInProgress = 0;    
    this._maxConcurrentRequest = 2;
}

Global._ConnectionManager.prototype =
{
    enqueueRequestDelegate : function(requestDelegate)
    {
        this._requestDelegateQueue.push(requestDelegate);
        this._request();
    },
    
    next : function()
    {
        this._requestInProgress --;
        this._request();
    },
    
    _request : function()
    {
        if (this._requestDelegateQueue.length <= 0) return;
        if (this._requestInProgress >= this._maxConcurrentRequest) return;
        
        this._requestInProgress ++;
        var requestDelegate = this._requestDelegateQueue.shift();
        requestDelegate.call(null);
    }
}

Global.ConnectionManager = new Global._ConnectionManager();

I build the component names ConnectionManager using pure JavaScript code without any dependence on any AJAX/JavaScript framework/library. If users want to use this component to manage the request, they should use enqueueRequestDelegate method to put a delegate into the queue. The delegate will be executed when there's none or only one request is running in the browser. And after receiving the response from the server, the user must call the next method to notify the ConnectionManager, and then the ConnectionManager will execute the next pending request delegate if the queue is not empty.

For example, if we are using Prototype framework to make ten AJAX calls continuously:

function requestWithoutQueue()
{
    for (var i = 0; i < 10; i++)
    {
        new Ajax.Request(
            url,
            {
                method: 'post',
                onComplete: callback
            });
    }
}
    
function callback(xmlHttpRequest)
{
    // do something
}

We'll use the ConnectionManager to queue the requests as follows:

function requestWithQueue()
{
    for (var i = 0; i < 10; i++)
    {
        var requestDelegate = function()
        {
            new Ajax.Request(
                url,
                {
                    method: 'post',
                    onComplete: callback,
                    onFailure: Global.ConnectionManager.next,
                    onException: Global.ConnectionManager.next
                });
        }
        
        Global.ConnectionManager.enqueueRequestDelegate(requestDelegate);
    }    
}

function callback(xmlHttpRequest)
{
    // do sth.
    Global.ConnectionManager.next();
}

Please note that we assign the next method to both the onFailure and onException callback handlers to guarantee that it will be called after receiving the response from the server, since the rest delegate in the queue will fail to execute and the system cannot raise a new call anymore if the next method hasn't been executed.

I send the file to my friend and several days later he told me that his customer said the component is hard to use. I agreed. It's really verbose and error prone. Apparently the ConnectionManager is not so convenient to be integrated into the existing codes. The devs must make sure that all the requests should be queued in ConnectionManager and the next method must be executed in any case when the request finishes. But it's far from enough yet. More and more AJAX applications will execute scripts created by the server. Perhaps the dynamically created file cannot be loaded successfully if the internet connection of client side is not stable enough. At that time, the scripts execution throws exceptions and the next method which should be executed by design will probably be missed.

Build a Fake XMLHttpRequest Type

I got an idea after days of thinking. It will be perfect if we can use another component to replace the native XMLHttpRequest object and provide the built-in request queue. If so, devs can solve the problem by putting the script file in the page without changing a single line of code.

The solution is much easier than I thought before and now I'm going to show you how to build it.

The first thing we should do is to keep the native XHR type. Please note that the following code has solved the compatibility problem of XHR in different browsers:

window._progIDs = [ 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP' ];

if (!window.XMLHttpRequest)
{
    window.XMLHttpRequest = function()
    {
        for (var i = 0; i < window._progIDs.length; i++)
        {
            try
            {
                var xmlHttp = new _originaActiveXObject(window._progIDs[i]);
                return xmlHttp;
            }
            catch (ex) {}
        }
        
        return null;
    }
}

if (window.ActiveXObject)
{    
    window._originalActiveXObject = window.ActiveXObject;

    window.ActiveXObject = function(id)
    {
        id = id.toUpperCase();
        
        for (var i = 0; i < window._progIDs.length; i++)
        {
            if (id === window._progIDs[i].toUpperCase())
            {
                return new XMLHttpRequest();
            }
        }
        
        return new _originaActiveXObject(id);
    }
}

window._originalXMLHttpRequest = window.XMLHttpRequest;

And then, we should create a new class to replace the native XHR type. Most of the methods are just delegated to the corresponding one in the native object.

window.XMLHttpRequest = function()
{
    this._xmlHttpRequest = new _originalXMLHttpRequest();
    this.readyState = this._xmlHttpRequest.readyState;
    this._xmlHttpRequest.onreadystatechange = 
	this._createDelegate(this, this._internalOnReadyStateChange);
}

window.XMLHttpRequest.prototype = 
{
    open : function(method, url, async)
    {
        this._xmlHttpRequest.open(method, url, async);
        this.readyState = this._xmlHttpRequest.readyState;
    },
    
    setRequestHeader : function(header, value)
    {
        this._xmlHttpRequest.setRequestHeader(header, value);
    },
    
    getResponseHeader : function(header)
    {
        return this._xmlHttpRequest.getResponseHeader(header);
    },
    
    getAllResponseHeaders : function()
    {
        return this._xmlHttpRequest.getAllResponseHeaders();
    },
    
    abort : function()
    {
        this._xmlHttpRequest.abort();
    },
    
    _createDelegate : function(instance, method)
    {
        return function()
        {
            return method.apply(instance, arguments);
        }
    },
    
    _internalOnReadyStateChange : function()
    {
        // ...
    },
    
    send : function(body)
    {
        // ...
    }
}

The key points are the implementations of the send method and _internalOnReadyStateChange method. The send method will put a delegate of the native XHR type's method into the queue. The delegate will be executed by the ConnectionManager at a proper time.

send : function(body)
{
    var requestDelegate = this._createDelegate(
        this,
        function()
        {
            this._xmlHttpRequest.send(body);
            this.readyState = this._xmlHttpRequest.readyState;
        });
    
    Global.ConnectionManager.enqueueRequestDelegate(requestDelegate);
},

We assign the _internalOnReadyStateChange method as the onreadystatechange callback handler of the native XHR object in the constructor. When the callback function raises, we'll keep all the native properties into our object and execute our onreadystatechange handler. Please note that our new component takes the responsibility of executing the next method of ConnectionManager when the readyState equals to 4, which means the current request is "completed", so that the next method can be executed automatically from the devs' point of view.

_internalOnReadyStateChange : function()
{
    var xmlHttpRequest = this._xmlHttpRequest;
    
    try
    {
        this.readyState = xmlHttpRequest.readyState;
        this.responseText = xmlHttpRequest.responseText;
        this.responseXML = xmlHttpRequest.responseXML;
        this.statusText = xmlHttpRequest.statusText;
        this.status = xmlHttpRequest.status;
    }
    catch(e){}
    
    if (4 == this.readyState)
    {
        Global.ConnectionManager.next();
    }
    
    if (this.onreadystatechange)
    {
        this.onreadystatechange.call(null);
    }
}

We have tried our best to let the new component behave the same as the native XHR type. But it still exists. It is a little thing but we can't do it. When we access the status property in the native XHR object, an error would be thrown if the object cannot receive the headers from server side. But in Internet Explorer, we can't define the object's property as a method like using __setter__ keyword in FireFox. It's the only difference between the native XHR type and our new one when using the two components.

How to Use

And now, we can easily reference the JS file in the page to solve the problem when the user browses the page using Internet Explorer.

<!--[if IE]>
    <script type="text/javascript" src="ConnectionManager.js"></script>
    <script type="text/javascript" src="MyXMLHttpRequest.js"></script>
<![endif]-->

I sent the script file to my friend. It seems that his customer is quite pleased with this solution.

History

  • 21st June, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jeffrey Zhao


Member
Jeffrey Zhao(http://blog.jeffzon.net) has been a developer since 1997 and is working as a developer in .NET platform since 2002. He's now a develop manager in charge of building a media platform in a Chinese company. He's also a part-time technical consultant & trainer focus on .NET, AJAX and SilverLight technologies.
Occupation: Web Developer
Location: China China

Other popular Ajax and Atlas articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 7 of 7 (Total in Forum: 7) (Refresh)FirstPrevNext
GeneralAjax response only once Pinmemberjust9doit16:03 12 Feb '09  
GeneralIT WORKS ON IE6 Pinmembershukri Kassissieh1:41 6 Sep '07  
GeneralDid you test your code in IE6? PinmemberSeanBlagsvedt7:13 19 Jul '07  
GeneralGreat Article! PinmemberHenry Liang9:37 21 Jun '07  
GeneralAnother Solution PinmemberVectorX8:20 21 Jun '07  
GeneralRe: Another Solution PinmemberJeffrey Zhao9:13 21 Jun '07  
GeneralRe: Another Solution PinmemberThomas Lykke Petersen20:27 21 Jun '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.

PermaLink | Privacy | Terms of Use
Last Updated: 21 Jun 2007
Editor: Deeksha Shenoy
Copyright 2007 by Jeffrey Zhao
Everything else Copyright © CodeProject, 1999-2010
Web22 | Advertise on the Code Project