8.4% of all hangs in IE9 in the past month are caused by XMLHttpRequest objects blocking the UI thread with a synchronous request. That’s a huge number! With some manageable code changes, these hangs can be avoided, and developers can give their users a better experience across their websites. We’ll get into what’s happening here, what you can do about it, and we’ll give a little demonstration to see firsthand what can happen when a synchronous request hangs the browser.

Synchronous XMLHttpRequest Blocks the UI Thread

We already know performing a blocking operation on a UI thread is asking for trouble. We’ve blogged about this before. It might not be apparent, but this is exactly what can happen when calling the XMLHttpRequest.send() method synchronously (i.e. passing false for bAsync when calling the open method). The UI thread has to wait until it gets a response from the server, or until the request times out. While waiting, it’s not pumping messages.

Why Use Synchronous XMLHttpRequest At All?

Developers tend to use synchronous over asynchronous is because it’s easier and can many times lead to less complex code. This StackOverflow thread states some potentially valid opinions on why you’d use one over the other, but nothing compelling enough that makes it worth the risk.

Fixing It…

There are at least two ways you can write your code to avoid these hangs. The first method is preferred, because you won’t block on the UI thread. The second is less than optimal, because you’ll still block, but for less time than you’re probably doing now.

  1. Write your code to call open asynchronously.
    You need to pass true for the bAsync parameter to the open method, and you need to write an event handler for the onreadystatechange event. There are many examples of how to do this out one the web, like this one.
  2. Set the timeout property.
    Because the Desktop Window Manager detects a hang after 5 seconds of unresponsiveness, and IE9 Hang Resistance after 8, I would set the timeout property to something under 5 seconds. You may also want to consider creating a function to handle the ontimeout event.
    (How did I get these numbers? Refer to here and here respectively – also already mentioned above)

How About an Example?

Let’s dig into the details by creating a couple of scenarios:

  1. We’ll create some code that will make a synchronous request and hang the browser, and then
  2. change the code to be asynchronous, and observe the acute change in browser behavior.

The goal of these scenarios is to demonstrate how switching from synchronous to asynchronous requests will improve the user experience of your web applications.

DISCLAIMER: This will hang your browser and may result in unintended, perhaps even undesirable behavior on both the client and the server. In other words, don’t run this on your production server, or any other machine that can’t tolerate running instable and potentially broken code.

 The Setup

You’ll need a web server capable of running ASP.NET if you want to follow the example directly. I realize some readers may not be using ASP.NET, but the code should be straight-forward enough that you can adapt it to other environments without too much trouble.

Also, I will assume you are familiar with your web server environment and know how to render a page server-side.

Copy this code into your favorite text editor and save it as hangme.aspx somewhere on your web server. 

 

 <!-- code starts after this line -->
<%@ Page Language="C#" %>
<html>
<head>
<title>XmlHttpRequest open hang test</title>
<script runat="server">
   protected void Page_Load(object sender, EventArgs e) {
      if(Request.QueryString["hang"] == "1") {
         int seconds = 0;
         Int32.TryParse(Request.QueryString["seconds"], out seconds);
         System.Threading.Thread.Sleep(seconds * 1000);
      }
   }
</script>
<script type="text/javascript">
   function call_hangme() {
      var oReq;       if (window.XMLHttpRequest) {
         oReq = new XMLHttpRequest();
      }
      if (oReq != null) {
         var sUrl = "http://localhost/hangme.aspx?hang=1&seconds=360";

         <!-- change localhost to your server name if applicable -->
         document.getElementById("txt1").innerHTML = "Request Sent...";

         <!-- pass false for the bAsync parameter for a synchronous request -->
            oReq.open("GET", sUrl, false);

         oReq.send();
         document.getElementById("txt1").innerHTML = "Response Received!";
      }
      else {
         window.alert("Error creating XmlHttpRequest object.");
      }
   }
</script>
</head>
<body>
   <p>Click this button to hang the browser</p>
   <form name="form1" action="" method="get">
      <input type="button" name="btn1" value="hang me" onClick="call_hangme()">
   </form>
   <p id="txt1"/>
</body>
</html>
<!-- code ends on the line before this one -->

 Make It Hang

Now,

  1. Open Internet Explorer and browse to the hangme.aspx page.
  2. Click the “hang me” button.

Uh-oh! We’re hung! Because we told the thread to sleep for 6 minutes, we’re going to be here awhile. If you’re using IE9, you’ve probably noticed the gold band at the bottom offering to “Recover webpage”. If you’re using another browser, you’ve probably got a ghosted window.

Calling Open Asynchronously

Copy this code new file and save it as wont_hangme.aspx

 <!-- code starts after this line -->  
<%@ Page Language="C#" %>
<html>
<head>
<title>XmlHttpRequest open hang test</title>
<script runat="server">
   protected void Page_Load(object sender, EventArgs e) {
      if(Request.QueryString["hang"] == "1") {
         int seconds = 0;
         Int32.TryParse(Request.QueryString["seconds"], out seconds);
         System.Threading.Thread.Sleep(seconds * 1000);
      }
   }
</script>
<script type="text/javascript">
   function hangme() {
      var oReq;
      if (window.XMLHttpRequest) {
      oReq = new XMLHttpRequest();
      }

      if (oReq != null) {

         <!-- change localhost to your server name if applicable -->
         var sUrl = "http://localhost/wont_hangme.aspx?hang=1&seconds=500";
         document.getElementById("txt1").innerHTML = "Request Sent...";

         <!-- pass true for the bAsync parameter -->
         oReq.open("GET", sUrl, true);

         <!-- Here we define an anonymous function for the
           onreadystatechange event.
We check if we received all the data,
>           and the request was successful before changing the text

         -->

         oReq.onreadystatechange = function() {
            if (oReq.readyState == 4 && oReq.status == 200) {
               document.getElementById("txt1").innerHTML = "Response Received!";
            }
         }

         oReq.send();
      }
      else {
         window.alert("Error creating XmlHttpRequest object.");
      }
   }
</script>
</head>
<body>
   <p>Click this button to hang the browser</p>
   <form name="form1" action="" method="get">
      <input type="button" name="btn1" value="hang me" onClick="hangme()">
   </form>
   <p id="txt1"/>
</body>
</html>
<!-- code ends on the line before this one -->

The new code is highlighted. Here, we’re now calling open with bAsync = true, which means we’re making an asynchronous call. This will free up the UI thread to do other things, instead of it being blocked waiting for the response or timeout. If we’re not blocked, we can pump messages, and if we can do that, we’re not hung!

Now, when the response eventually comes back from the server, we’ll need a function to handle it. That is what the highlighted code does. It’s our event handler for onreadystatechange event. As you can see, I’m only checking that there response was successful (status == 200) and that all the data has been received (readyState == 4).

Can We Make It Hang Now?

Again,

  1. Open Internet Explorer and browse to the cant_hangme.aspx page.
  2. Click the “hang me” button.

Voilà! You’ll see the request is sent, but the browser is not hung. When the response finally makes its way back, the text will change; all the while you’re still able to interact with the page.

In Closing

As stated from the outset, you want to implement your XMLHttpRequest calls asynchronously. This example clearly demonstrates why; you’ll improve the responsiveness of your pages, avoid hangs, and most importantly, give your users a better experience.