Comparing SPServices 2013.01 Calls with Async vs. Promises Methods

SPServices 2013.01 is almost ready for release and I’m hoping that when you see the coolness here that you’ll want to get your hands on the beta to do a little testing with it.

I’ve put together a pretty simple demo page to show how great it can be to use jQuery .Deferred objects (aka promises) to retrieve data with SPServices versus the asynchronous way we’ve tended to use until now. The demo page is running on my old WSS 3.0 site hosted at FPWeb. Unfortunately, I don’t have a public-facing SharePoint 2013 instance anywhere yet.

The idea here is pretty simple. I wanted to keep the complexity of the code pretty low and also to use as much common code as possible between the two methods so that the real apples to oranges comparison is the async method versus the promises method.

I’ve set up three Announcements lists in a single site: Corporate Announcements, Finance Announcements, and HR Announcements. Each list has three announcements in it. I even got to use Bacon Ipsum to fill in some dummy content.

The demo page has two buttons on it: one for async and one for promises. When you push one of the buttons, the script loops through the array containing information about the three lists, retrieves the three items from each of the lists using the chosen method, and displays them on the page. There’s a slightly different version of the code in each of the functions (getRecentAnnouncementsAsync and getRecentAnnouncementsPromises) that run in each case.

The getRecentAnnouncementsAsync function runs the way we’re all used to using SPServices. The async option is set to false and processing of the returned data happens inside the completefunc.

In the promises version, the async option isn’t set, so it has the default of true (we don’t wait for the returned data, but continue processing). The promise returned from each call to GetListItems is put into an array, and the line $.when.apply($, announcementsPromises).done(...) is where the check for all three promises to be complete happens. When that occurs, the data for the three calls is processed.

Here’s the code:

// Show the most recent Annoucements from three lists in the current site
var announcementsPerList = 3;
var lists = [{
    name : "Corporate Announcements"
  }, {
    name : "HR Announcements"
  }, {
    name : "Finance Announcements"
  }
];
var camlQuery = "<Query><OrderBy><FieldRef Name='Created' Ascending='FALSE'/></OrderBy></Query>";
var camlViewFields = "<ViewFields>" +
  "<FieldRef Name='ID' />" +
  "<FieldRef Name='Title' />" +
  "<FieldRef Name='Body' />" +
  "<FieldRef Name='Author' />" +
  "<FieldRef Name='Created' />" +
  "<FieldRef Name='FileDirRef' />" +
  "</ViewFields>";
var out;

$(document).ready(function () {

  //$("#ctl00_site_share_button").hide();
  $("input[value='async']").click(function () {

    getRecentAnnouncementsAsync();

  });

  $("input[value='promises']").click(function () {

    getRecentAnnouncementsPromises();

  });

});

//++++++++++++++++++++++++++++++++++++

function getRecentAnnouncementsAsync() {

  var recentAnnouncementsContainer = $("#demo-recent-announcements");
  recentAnnouncementsContainer.empty();
  recentAnnouncementsContainer.addClass("demo-loading");

  out = "<table>";
  announcementHeader();

  for (var i = 0; i < lists.length; i++) {

    $().SPServices({
      operation : "GetListItems",
      async : false,
      listName : lists[i].name,
      CAMLQuery : camlQuery,
      CAMLViewFields : camlViewFields,
      CAMLRowLimit : announcementsPerList,
      completefunc : function (xData) {

        // List header
        out += "<tr><td class='demo-section-header' colspan='99'>" + lists[i].name + "</td></tr>";

        $(xData.responseXML).SPFilterNode("z:row").each(function (itemNum) {

          processAnnouncement(i, $(this));

        });

      }
    });

  }

  out += "</table>";
  $("#demo-recent-announcements").removeClass("demo-loading");
  recentAnnouncementsContainer.html(out);

}
function getRecentAnnouncementsPromises() {

  var recentAnnouncementsContainer = $("#demo-recent-announcements");
  recentAnnouncementsContainer.empty();
  var announcementsPromises = [];
  recentAnnouncementsContainer.addClass("demo-loading");

  out = "<table>";
  announcementHeader();

  for (var i = 0; i < lists.length; i++) {

    announcementsPromises[i] = $().SPServices({
        operation : "GetListItems",
        listName : lists[i].name,
        CAMLQuery : camlQuery,
        CAMLViewFields : camlViewFields,
        CAMLRowLimit : announcementsPerList
      });

  }

  // When all of the promises are fulfilled...
  $.when.apply($, announcementsPromises).done(function () {
    for (var i = 0; i < lists.length; i++) {

      // List header
      out += "<tr><td class='demo-section-header' colspan='99'>" + lists[i].name + "</td></tr>";

      $(announcementsPromises[i].responseXML).SPFilterNode("z:row").each(function (itemNum) {

        processAnnouncement(i, $(this));

      });

    }

    out += "</table>";
    $("#demo-recent-announcements").removeClass("demo-loading");
    recentAnnouncementsContainer.html(out);

  });

}

function getNow() {

  return new Date();

}

function announcementHeader() {

  out += "<tr class='ms-WPHeader'><td colspan='99'><h3 class='ms-standardheader ms-WPTitle'>Recent Announcements as of " + getNow() + "</h3></td></tr>";
  out += "<tr>" +
  "<th class='ms-vh2'>Title</th>" +
  "<th class='ms-vh2'>Body</th>" +
  "<th class='ms-vh2'>Created By</th>" +
  "<th class='ms-vh2'>Created</th>" +
  "</tr>";

}

function processAnnouncement(i, item) {

  out += "<tr>";

  // Title as a link to the announcement
  var thisLink = item.attr("ows_FileDirRef").split(";#");
  out += "<td class='ms-vb demo-note-details' style='width:40%;'>" +
  "<a href='/" + thisLink[1] + "/DispForm.aspx?ID=" + thisLink[0] + "&Source=" + location.href + "' data-announcement-id='" + item.attr("ows_ID") +
  "' data-list='" + lists[i].name + "' >" +
  item.attr("ows_Title") +
  "</a>" +
  "</td>";

  // Body
  var thisBody = item.attr("ows_Body");
  out += "<td class='ms-vb'>" + ((typeof thisBody !== "undefined") ? thisBody : "NA") + "</td>";

  // Author as a link to the User Profile
  var thisAuthor = item.attr("ows_Author").split(";#");
  out += "<td class='ms-vb' style='width:15%;'>" +
  "<a href='/_layouts/userdisp.aspx?ID=" + thisAuthor[0] + "' onclick='GoToLink(this);return false;'>" + thisAuthor[1] + "</a>" +
  "</td>";

  // Created date/time
  out += "<td class='ms-vb' style='width:15%;'>" + item.attr("ows_Created") + "</td>";

  out += "</tr>";

}

Note that I’m using the same version of SPServices here. SPServices 2013.01 can be used as you’re used to using it and everything will work as you are used to. If you are making multiple calls and the promises method would make more sense (mainly in terms of efficiency), then you can take advantage of it.

There are two big benefits of using the promises method:

  • If there are multiple calls to the Web Services that can logically happen concurrently (there’s less benefit if there is only one call), the promises method allows that to happen.
  • In the promises method, the browser doesn’t get locked up like it does with the async method. Setting async to true means that the browser is going to wait for the results to come back from the call before it will allow anything else to happen. With promises, the processing continues even when the results aren’t available. (This can take some getting used to, and I’ll undoubtedly do more posts about it.)

I’ve tested the code in SharePoint 2013 on Office365 and WSS 3.0 at FPWeb, which are about the most opposite ends of the spectrum that we can try these days. The code that I tested is *exactly* the same in the two environments. I simply copied and pasted the file from SharePoint 2013 where I started into the WSS 3.0 environment. How’s that for consistency, even on a downgrade? Oh, if Microsoft’s code only did that!

Using Firebug, I’ve captured information from the two methods on the Net tab for the XHR traffic.

Here are the results in SharePoint 2013:

Async

image

Promises

image

As you can see the total elapsed time decreased from about 503ms to 107ms. These results are fairly consistent. I haven’t tried to build any fancy instrumentation around it, but if I click the buttons repeatedly, I get consistent results in both cases.

Here are the results from WSS 3.0:

Asynch

image

Promises

image

Here, the total elapsed time decreased from about 409ms to 130ms.

In both versions of SharePoint, you can quite obviously see the difference. With the async method, the three calls happen in order, whereas with the promises method, they happen pretty much concurrently. (Even with promises, there’s a small lag due to the processing to set up the calls.)

But don’t take my word for it. go and grab your own copy of the

12 Comments

  1. Hey Marc,

    I really like such tests and its good to know that Promises work concurrently.

    My biggest problem with async and concurrent code is the end result, how do you force a certain order, filtering, … so that your UI isn’t cluttered ?

    Any big hints ?

    Kind regards,

    Pascal

    Reply
  2. Marc, it seems that we can never agree on the terminology :-). Both methods are asynchronous, I think the first one is usually referred to as the callback method.

    Very nice to see the traffic screenshots and how promises save time thanks to concurrent loading. I found the bacon ipsum link very useful too…

    Reply
  3. Thanks as always for a detailed, insightful post. I just spent the last couple weeks learning jQuery deferreds/promises and I’ve been using SPServices 2013.01.

    If only I had known $().SPServices() returned a promise! (I went through a lot of hoops to build my own $.when.apply($, deferreds).done() to wrap multiple SPServices calls by creating my own deferreds and kicking off the SPService calls using setTimeout – LOL)

    Personally I find the Gary Busey ipsum generator more entertaining ;)

    http://www.buseyipsum.com/

    Reply
  4. Hi Marc,

    Thanks a lot, as you said, 7 mins read will help me a lot to understand this promises in SP Services.

    But I found an issue while testing in IE11, SharePoint 2013 on premises.
    announcementsPromises[i] is showing undefined.

    Please help me out.

    Reply

Have a thought or opinion?