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:

1// Show the most recent Annoucements from three lists in the current site
2var announcementsPerList = 3;
3var lists = [{
4    name : "Corporate Announcements"
5  }, {
6    name : "HR Announcements"
7  }, {
8    name : "Finance Announcements"
9  }
10];
11var camlQuery = "<Query><OrderBy><FieldRef Name='Created' Ascending='FALSE'/></OrderBy></Query>";
12var camlViewFields = "<ViewFields>" +
13  "<FieldRef Name='ID' />" +
14  "<FieldRef Name='Title' />" +
15  "<FieldRef Name='Body' />" +
16  "<FieldRef Name='Author' />" +
17  "<FieldRef Name='Created' />" +
18  "<FieldRef Name='FileDirRef' />" +
19  "</ViewFields>";
20var out;
21 
22$(document).ready(function () {
23 
24  //$("#ctl00_site_share_button").hide();
25  $("input[value='async']").click(function () {
26 
27    getRecentAnnouncementsAsync();
28 
29  });
30 
31  $("input[value='promises']").click(function () {
32 
33    getRecentAnnouncementsPromises();
34 
35  });
36 
37});
38 
39//++++++++++++++++++++++++++++++++++++
40 
41function getRecentAnnouncementsAsync() {
42 
43  var recentAnnouncementsContainer = $("#demo-recent-announcements");
44  recentAnnouncementsContainer.empty();
45  recentAnnouncementsContainer.addClass("demo-loading");
46 
47  out = "<table>";
48  announcementHeader();
49 
50  for (var i = 0; i < lists.length; i++) {
51 
52    $().SPServices({
53      operation : "GetListItems",
54      async : false,
55      listName : lists[i].name,
56      CAMLQuery : camlQuery,
57      CAMLViewFields : camlViewFields,
58      CAMLRowLimit : announcementsPerList,
59      completefunc : function (xData) {
60 
61        // List header
62        out += "<tr><td class='demo-section-header' colspan='99'>" + lists[i].name + "</td></tr>";
63 
64        $(xData.responseXML).SPFilterNode("z:row").each(function (itemNum) {
65 
66          processAnnouncement(i, $(this));
67 
68        });
69 
70      }
71    });
72 
73  }
74 
75  out += "</table>";
76  $("#demo-recent-announcements").removeClass("demo-loading");
77  recentAnnouncementsContainer.html(out);
78 
79}
80function getRecentAnnouncementsPromises() {
81 
82  var recentAnnouncementsContainer = $("#demo-recent-announcements");
83  recentAnnouncementsContainer.empty();
84  var announcementsPromises = [];
85  recentAnnouncementsContainer.addClass("demo-loading");
86 
87  out = "<table>";
88  announcementHeader();
89 
90  for (var i = 0; i < lists.length; i++) {
91 
92    announcementsPromises[i] = $().SPServices({
93        operation : "GetListItems",
94        listName : lists[i].name,
95        CAMLQuery : camlQuery,
96        CAMLViewFields : camlViewFields,
97        CAMLRowLimit : announcementsPerList
98      });
99 
100  }
101 
102  // When all of the promises are fulfilled...
103  $.when.apply($, announcementsPromises).done(function () {
104    for (var i = 0; i < lists.length; i++) {
105 
106      // List header
107      out += "<tr><td class='demo-section-header' colspan='99'>" + lists[i].name + "</td></tr>";
108 
109      $(announcementsPromises[i].responseXML).SPFilterNode("z:row").each(function (itemNum) {
110 
111        processAnnouncement(i, $(this));
112 
113      });
114 
115    }
116 
117    out += "</table>";
118    $("#demo-recent-announcements").removeClass("demo-loading");
119    recentAnnouncementsContainer.html(out);
120 
121  });
122 
123}
124 
125function getNow() {
126 
127  return new Date();
128 
129}
130 
131function announcementHeader() {
132 
133  out += "<tr class='ms-WPHeader'><td colspan='99'><h3 class='ms-standardheader ms-WPTitle'>Recent Announcements as of " + getNow() + "</h3></td></tr>";
134  out += "<tr>" +
135  "<th class='ms-vh2'>Title</th>" +
136  "<th class='ms-vh2'>Body</th>" +
137  "<th class='ms-vh2'>Created By</th>" +
138  "<th class='ms-vh2'>Created</th>" +
139  "</tr>";
140 
141}
142 
143function processAnnouncement(i, item) {
144 
145  out += "<tr>";
146 
147  // Title as a link to the announcement
148  var thisLink = item.attr("ows_FileDirRef").split(";#");
149  out += "<td class='ms-vb demo-note-details' style='width:40%;'>" +
150  "<a href='/" + thisLink[1] + "/DispForm.aspx?ID=" + thisLink[0] + "&Source=" + location.href + "' data-announcement-id='" + item.attr("ows_ID") +
151  "' data-list='" + lists[i].name + "' >" +
152  item.attr("ows_Title") +
153  "</a>" +
154  "</td>";
155 
156  // Body
157  var thisBody = item.attr("ows_Body");
158  out += "<td class='ms-vb'>" + ((typeof thisBody !== "undefined") ? thisBody : "NA") + "</td>";
159 
160  // Author as a link to the User Profile
161  var thisAuthor = item.attr("ows_Author").split(";#");
162  out += "<td class='ms-vb' style='width:15%;'>" +
163  "<a href='/_layouts/userdisp.aspx?ID=" + thisAuthor[0] + "' onclick='GoToLink(this);return false;'>" + thisAuthor[1] + "</a>" +
164  "</td>";
165 
166  // Created date/time
167  out += "<td class='ms-vb' style='width:15%;'>" + item.attr("ows_Created") + "</td>";
168 
169  out += "</tr>";
170 
171}

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

Similar Posts

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

  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…

      1. Even worse, you influence others! I was tempted to call my new set of tools “second-party solutions”, I’m sure my brain got too much exposure to the “middle tier”…

  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/

  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.

Leave a Reply to Chris Norton Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.