Improve jQuery Performance by using Asynchronous AJAX

In a recent project, I needed to make some calls using AJAX to enable and disable buttons in a data view. The data view is presenting a list of individuals filtered by the first letter of the last name so that tabs can be provided for each group of individuals. To improve the user experience, the team decided to fade tabs that currently had no items listed. This meant that I need to make 26 web service calls to SharePoint for each letter of the alphabet.

Of course, the best way to interact with web services using jQuery is to download Marc Anderson’s (@sympmarc) SPServices jQuery Library from CodePlex. With this in hand, I started building my code to execute the queries.

First, my data view creates an unordered list with each letter of the alphabet. This ties into a dataview looking for the querystring D to filter values.

<div id="FilterSelector">
	<div id="Loader">
		Loading data...
	</div>
	<span id="FilterLabel">Last Name Starts With: </span>
	<ul id="FilterSelectorTabs">
		<li><a href="?D=A" id="SELECT_A">A</a></li>
		<li><a href="?D=B" id="SELECT_B">B</a></li>
		<li><a href="?D=C" id="SELECT_C">C</a></li>
		<li><a href="?D=D" id="SELECT_D">D</a></li>
		<li><a href="?D=E" id="SELECT_E">E</a></li>
		<li><a href="?D=F" id="SELECT_F">F</a></li>
		<li><a href="?D=G" id="SELECT_G">G</a></li>
		<li><a href="?D=H" id="SELECT_H">H</a></li>
		<li><a href="?D=I" id="SELECT_I">I</a></li>
		<li><a href="?D=J" id="SELECT_J">J</a></li>
		<li><a href="?D=K" id="SELECT_K">K</a></li>
		<li><a href="?D=L" id="SELECT_L">L</a></li>
		<li><a href="?D=M" id="SELECT_M">M</a></li>
		<li><a href="?D=N" id="SELECT_N">N</a></li>
		<li><a href="?D=O" id="SELECT_O">O</a></li>
		<li><a href="?D=P" id="SELECT_P">P</a></li>
		<li><a href="?D=Q" id="SELECT_Q">Q</a></li>
		<li><a href="?D=R" id="SELECT_R">R</a></li>
		<li><a href="?D=S" id="SELECT_S">S</a></li>
		<li><a href="?D=T" id="SELECT_T">T</a></li>
		<li><a href="?D=U" id="SELECT_U">U</a></li>
		<li><a href="?D=V" id="SELECT_V">V</a></li>
		<li><a href="?D=W" id="SELECT_W">W</a></li>
		<li><a href="?D=X" id="SELECT_X">X</a></li>
		<li><a href="?D=Y" id="SELECT_Y">Y</a></li>
		<li><a href="?D=Z" id="SELECT_Z">Z</a></li>
	</ul>
</div>

My first attempt was done synchronously calling the lists web service to get a count of items. To do this, I simply iterated through each item in my unordered list and called the web service with an appropriate CAML query:

function getAdditionalData(tabs)
{
		$(tabs).find("li").each(function () {
			var item = $(this).find("a");
			var itemValue = item.text();
			var itemCount = getItemCounts(itemValue);
			if(itemCount == '0')
			{
				item.attr("isempty","yes");
				item.attr("title","There are currently no tributes found.");
			}
			else
			{
				item.attr("isempty","no");
				item.attr("title",itemCount + " tribute(s) found.");
			}
		});
}

function getItemCounts(displayValue)
{
	var countOfItems = 0;
	$().SPServices({
		operation: "GetListItems",
		async: false,
		listName: "Contacts",
		CAMLQuery: "<Query><Where><And><BeginsWith><FieldRef Name=\"lastname\" /><Value Type=\"Text\">" + displayValue + "</Value></BeginsWith><Eq><FieldRef Name=\"ContactStatus\" /><Value Type=\"Choice\">Approved</Value></Eq></And></Where></Query>",
		completefunc: function (xData, Status) {
			if(Status == "success") {
				countOfItems = $(xData.responseXML).find("[nodeName=rs:data]").attr("ItemCount");
			}
		}
	});
	return countOfItems;
}

This worked perfectly, but ended up locking the browser until all 26 queries were executed. As the list continued to grow, this delay became more and more noticable (and we’re only at 280 items!!). Each of the 26 calls required around 200 ms on average. Doing some quick math, you can see that the load time for the page was roughly 5,200 ms or about 5 seconds. This is unacceptable and could lead to poor reception from our user community. It was necessary to review the code to see if we could get some performance gains.

The team decided the best way to get some performance gains would be to make the web call asynchronously. When the AJAX call completes, allow the user interface to be updated at that time. Here’s the code we used:

	function getAdditionalData(tabs)
	{
		tabs.find("li").each(function () {
			var item = $(this).find("a");
			var itemValue = item.text();
			var Query = "<Query><Where><And><BeginsWith><FieldRef Name=\"lastname\" /><Value Type=\"Text\">" + itemValue + "</Value></BeginsWith><Eq><FieldRef Name=\"ContactStatus\" /><Value Type=\"Choice\">Approved</Value></Eq></And></Where></Query>";
			$().SPServices({
				operation: "GetListItems",
				async: true,
				listName: "Contacts",
				CAMLQuery: Query,
				completefunc: function (xData, Status) {
					if(Status == "success")
					{
						var itemCount = $(xData.responseXML).find("[nodeName=rs:data]").attr("ItemCount");
						if(itemCount == '0')
						{
							item.fadeTo(100,.3);
							item.attr("title","There are currently no tributes found.");
						}
						else
						{
							item.attr("title", itemCount + " tribute(s) found");
						}
					}
					if(itemValue == "Z")
					{
						$("#Loader").fadeOut(400);
					}
				}
			});
		});
	}

By making the web service calls asynchronously, the loading time of the page has reduced to around 200 ms. Also, the page is fully responsive while the web service calls are occuring, so if the user chooses to navigate to another tab they do not have to wait until all of the data has been processed by the server.

— UPDATED —
To help clarify the above statement, the page is completely ready for browsing with all web service calls completed in about 200 ms.

11 thoughts on “Improve jQuery Performance by using Asynchronous AJAX

  1. Chris:

    I’m not certain that this is a great place to use the Web Services. As I tweeted to you, I did something similar to this quite a long time ago, but fully in the DVWP. Obviously our requirements aren’t exactly the same, but the 26 Web Services calls concern me a bit. On the other hand, if you can paint the screen for the user and then asynchronously do those 26 calls it might work fine. My expereince, though, is that DVWPs render darn fast, and by keeping the rendering server-side you might be better off.

    I know, hard to believe that I would try to dissuade you from useing the Web Services, but just because you can doesn’t mean you should. ;+)

    M.

    1. Marc,

      I agree that in most circumstances, if you can allow the server to do the work, let it. However, in this circumstance, the need to grey-out individual items and place a count of items found on the tab pushed me to use the web services. My concern at iterating through all items at a time with the dataview has more to do with the final size of entries. We can confirm that if all entries are completed, there will be at least 2,200 items in the list. We are already over 300 and the solution has been live for 4 days.

      I’ve found that even executing 26 asynchronous calls, the solution is very responsive and load time is less than 200 ms. It does place a little more work on the client.

      I will be reusing some of this along with my post concerning getting available choices from a choice field (https://devspoint.wordpress.com/2010/10/26/using-spservices-to-get-the-values-of-a-choice-field/) to do the same type of tabbed interface. However, these will only be around six queries – a much lighter load on the client.

      1. Chris:

        I wouldn’t worry about the DVWP with 2200 items. That “2000 item limit” is a total SharePoint myth; I’ve had DVWPs be performant with tens of thousands of items, even in AggregateDataSources.

        The greying out and counts can still be done in the DVWP simply by adding some conditional logic into the DVWP. As usual, there’s more than one way to skin this cat, of course.

        M.

      2. Chris, is your load time measured on a test machine, or is it a real life scenario including remote users and the network? 200 ms seems pretty fast for these 26 calls.

    2. I re-read the post, and now I am confused: is 200 ms for one call, or for the 26? Even with asynchronous calls, the browser won’t execute all of them simultaneously, right?

      1. The total load time of the page is around 200 ms across the internal network. Each of the 26 asynchronous calls happen pretty much simultaneously, but the important thing is the UI doesn’t get blocked. This is currently running in a production environment with a large user base. The experience is pretty much the same for all users in the organization regardless of their location and hardware.

        Also, I have tested this across a VPN connection and the duration increases, but it is still very tolerable. I don’t have any metrics for it at this time, but the SharePoint chrome loads after the web service calls have completed.

      2. Chris, just a quick follow up: I double checked, and browsers have a limitation to the number of concurrent AJAX calls. For example IE 8 cannot have more than 6. Based on this, 26 calls will take 5 batches (6-6-6-6-2).

    1. Christophe,

      This is a SharePoint 2007 solution, so if the list contains less than 2,000 items I would agree. However, this list has the potential to grow over 2,000 items fairly quickly. It went from 65 items to almost 300 items in 2 days (last Thursday and Friday). I’m hoping that by executing 26 queries, the overall performance will remain tolerable as the list grows.

      Chris

  2. Nice! I have been finding this out as well in some of my tasks. It is always nice when the users do not have to wait!

Leave a reply to Marc Cancel reply