0

Using JavaScript Promises with SharePoint 2013 – Deferring executeQueryAsync to be a little Synchronous

If you have worked with Asynchronous programming, its no brainier that you might have enjoyed writing highly performing apps.

But I am sure you might have felt at some rare occasions that “Alas! I wish if this small portion of my code works synchronously and dammit wait for an action to complete!”. 

Most of the time its always fire-and-forget scenario with asynchronous calls, but there are certain scenarios where you want your code to return something. Especially this gets even tricky if you are working with asynchronous logic within loops. JavaScript Promise comes to your rescue!

This blog post is not meant to educate you on JQuery or JavaScript Promises, but just to give you an idea of something like this exists and how you can use it with SharePoint. I will not leave you disappointed, following are some good articles that will get you started.

Article 1: http://blog.qumsieh.ca/2013/10/31/using-jquery-promises-deferreds-with-sharepoint-2013-jsom/

Article 2: http://www.vasanthk.com/jquery-promises-and-deferred-objects/

Spread the word for your fellow developers

“JavaScript Promise 

I was working on a typical use-case of updating SharePoint list items for a SharePoint Online site. Let me explain the use case in detail, I have two SharePoint lists, one is a SharePoint Document Library and the other is a Custom list. Document library has to be updated based on certain values from the Custom list. Of course, the data has to be manipulated based on certain conditions and multiple business rules before updating the document library.

Initial thought process would be to use any of the tools at disposal or use out-of-the-box features to get this working, but after an exhaustive research over the internet, I could not find any way to accomplish this with no customization. So as a last resort, I decided to write some code.

NOTE: This is SharePoint online site, REST API or JSOM (Java Script Object Model) would be the default choices. I am using JavaScript for this example.

Following is the general thought process

Step 1: Get the list of items asynchronously  from Source List (i.e. SharePoint Custom List)

Step 2: Loop through the items and update the Destination List (i.e. SharePoint Document Library)

Ideally everything should work seamlessly, but Step 2 will fail with asynchronous way of programming. When you loop through the items that are fetched from Source List it works great, but any updates or actions that you perform with in a loop may not work.

Loop proceeds irrespective of action being complete or not. If you are dealing with one item and no-loop involved it works fine. But I am dealing with hundreds of items with too many of these loops.

Using JQuery Promise for SharePoint 2013

 

NOTE:

Following  code has clear and detailed inline comments. Everything should be self explanatory!

Following code has been developed for one-time use only and is meant for educational purposes only and not recommended for production use. I can say upfront that the naming conventions, error handling/logging are not production ready. You may need to make updates to the code and may require some re-work. Of course, there is always a better way doing things.Use at your own risk!


<!--Add JQuery reference-->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>

<script type="text/javascript">

//Get the source list items to loop through and update the destinantion list.
function GetSourceListItems() {

//Assign the name of the list to use as Source List
var strSourceListName = "SourceListName";

//Get the current SharePoint Context: Represents the context for objects and operations
var oContext = SP.ClientContext.get_current();

//Get the current web
var oWeb = oContext.get_web();

//Get the source list using the Name or GUID
var oList = oWeb.get_lists().getByTitle(strSourceListName);

//CAML Query to query the Source List
//Following is the Query to get items less than ID 800.
var strQuery = '<View><Query> <Where> <Leq> <FieldRef Name=\'ID\' \/> <Value Type=\'Counter\'>800<\/Value> <\/Leq> <\/Where> <\/Query><\/View>';
var oQuery = new SP.CamlQuery();
oQuery.set_viewXml(strQuery);

//Get the items based on Query
var allItems = oList.getItems(oQuery);
oContext.load(allItems);

//Make an asynchronous call.
oContext.executeQueryAsync(Function.createDelegate(this, function () { onQuerySuccess(allItems); }),
Function.createDelegate(this, this.onQueryFailed));
}

//On Query success loop through the items and update the destination list:
function onQuerySuccess(allItems) {

//Returns an enumerator that iterates through the SP.ClientObjectCollection Class.
var ListEnumerator = allItems.getEnumerator();

//Names of the columns in the source list to use
var sourceColumnArray = ["SourceListColumn1", "SourceListColumn2", "SourceListColumn3"];

//Iterate though list items and update the destination list.
while (ListEnumerator.moveNext()) {
//Get the current item
var currentItem = ListEnumerator.get_current();

//Create an array of values that should be used to update the destination list.
var valuesArray = [];

//Loop through the source column array and populate the array.
for (var i = 0; i < sourceColumnArray.length; i++) {
var value = currentItem.get_item(sourceColumnArray[i]);
valuesArray.push(value);
}

//Get the value of the common key that relates both the Source and Destination Lists.
//Use the same key to query against the destination list.
var commonKey = currentItem.get_item('commonColumnName');

//Check the length of array and send it update the destination list.
if (valuesArray.length > 0) {
UpdateDestinationListItem(commonKey, valuesArray);
}
}
}

//On Query failure log message
function onQueryFailed(sender, args) {
//Log Error
}

//Update the destination list
//Use common key to query the distination list
//Use values array to update the values of the desination list
function UpdateDestinationListItem(commonKey, valuesArray) {
//****Important 1.0****//
//Use of JQuery deferred
var d = $.Deferred();

//Give the name of the distination list to update with values.
var destinationListName = "DestinationListName";

//Use the key from the source list to query the destination list.
var strDistinationCAMLQuery = '<View><Query><Where><Eq><FieldRef Name=\'FileLeafRef\' \/> <Value Type=\'File\'>' + commonKey + '<\/Value> <\/Eq> <\/Where> <\/Query><\/View>';

//Get the current SharePoint Context: Represents the context for objects and operations
var oClientContext = SP.ClientContext.get_current();

//Get the current web
var oWeb = oClientContext.get_web();

//Get the destination list using the Name or GUID
var oDestinationlist = oWeb.get_lists().getByTitle(destinationListName);

//Specifies a Collaborative Application Markup Language (CAML) query on a list or joined lists.
var oCamlQuery = new SP.CamlQuery();
oCamlQuery.set_viewXml(strDistinationCAMLQuery);

//Get the list of items using the CAML Query
var oCollListItem = oDestinationlist.getItems(oCamlQuery);
oClientContext.load(oCollListItem);

//****Important 2.0****//
//Use of JQuery deferred
//Send list of items, client context, array of values from Source List
var o = { d: d, listOfItems: collListItem, clientContext: clientContext, valuesArray: valuesArray };

clientContext.executeQueryAsync(Function.createDelegate(o, successCallback), Function.createDelegate(o, failCallback));

return d.promise();

function successCallback() {

var itemCount = this.listOfItems.get_count();

var destinationColumnArray = ["DestinationListColumn1", "DestinationListColumn2", "DestinationListColumn3"];

var listItemInfo;

if (itemCount > 0) {
//****Important 3.0****//
//Use of JQuery deferred: Using 'listOfItems'
var listItemEnumerator = this.listOfItems.getEnumerator();

//Iterate though the list of items fetched from destination list
while (listItemEnumerator.moveNext()) {

var oCurrentListItem = listItemEnumerator.get_current();

for (var i = 0; i < destinationColumnArray.length; i++) {
//Write to console for debugging
console.log('Updating Value');

//****Important****//
//Destination list item getting updated.
oCurrentListItem.set_item(destinationColumnArray[i], this.valuesArray[i]);

//Write to console for debugging
console.log('Update Successfull');
}
oCurrentListItem.update();

this.clientContext.executeQueryAsync(onUpdateItemSuccess, onUpdateItemFailed);
}
}
}

function failCallback() {
this.d.reject("Failed at failCallback() method");
}

function onUpdateItemSuccess() {
console.log("Item Updated Sucessful");
}

function onUpdateItemFailed() {
console.log("Item Updated Failed");
}
}
</script>

I am not sure if this blog post helped you with what you wanted, but I would be glad if  you have discovered about JavaScript Promises for the first time via my blog post. Happy Coding!

Leave a Reply