1

Code that I can execute like this is my end fantasy:

var listCollection1 = getList("listName1");
var listCollection2 = getList("listName2");
// at this line I now have both of those populated

Where getList is just a function in a different file and I don't need to use globals and can get different lists on different pages and multiple lists.

My problem ultimately is the asynchronous query. I'm not used to async so let me know if my thinking is mistaken. Initially I was doing something like this:

var listCollection1;
getList("listName1");

function doSomething(){
    // at this line I now have listCollection1 populated
}

function getList(listName) {

    var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
    var currentcontext = new SP.ClientContext.get_current();
    var hostcontext = new SP.AppContextSite(currentcontext, hostUrl);

    var web = hostcontext.get_web(); // hostcontext instead of currentcontext
    var lists = web.get_lists();
    var list = lists.getByTitle(listName);
    var listItemCollection = list.getItems(""); // use CAML query for specific requests

    hostcontext.load(listItemCollection);
    hostcontext.executeQueryAsync(

        function () {
             alert("got my list now, let's do something");
             listCollection1 = listItemCollection;
             // or just pass it directly
             doSomething(listItemCollection);
        }, 

        function () {
             alert("didn't get my list");
        }
    );
}

But with this method, I can't load multiple lists without a bunch of if statements or different functions.

I also find the whole "go to this function" if it executes correctly "go to this function" if it doesn't style of programming difficult to follow and excessive global variables frustrating to deal with.

I found this question titled How to return a value using context.executeQueryAsync?, which outlines a callback method. But when I implemented that, it didn't really solve anything for me. I still couldn't seem to return the value from anywhere or get it to act synchronously.

And tried to do a callback thing to see if it got me any closer to what I was hoping for:

function getList(listName) {

    var get = function (callback) {

        var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));

        var currentcontext = new SP.ClientContext.get_current();
        var hostcontext = new SP.AppContextSite(currentcontext, hostUrl);
        var web = hostcontext.get_web(); // hostcontext instead of currentcontext

        var lists = web.get_lists();
        var list = lists.getByTitle(listName);

        listItemCollection = list.getItems(""); // use CAML query for specific requests

        currentcontext.load(listItemCollection);

        currentcontext.executeQueryAsync(function () {
            alert("asyn query successful");
            callback(null, listItemCollection);

        }, function (a, b) {
            alert("asyn query unsuccessful!")
            callback(new Error(b.get_message()));
        });
    };

    get(function (error, listItemCollection) {
        if (error) {
            alert(console.error(error));
            return;
        }

        console.log(listItemCollection); // it is here. I want to return it or send it somewhere from here
        return listItemCollection;
    });
}

But predictably it returns undefined and I'm in the same situation where I need to call another function to get out. Doesn't seem any different from the first method.

Alternatives or suggestions on how I can get something like what I have up top would be great. And if it's just not possible, thanks anyway.

Edit:

One thing that I've decided sort of works. Is passing a function as a parameter to getList that you want it to go to when successful. That way you can call

 getList("list1", functionForList1)
 getList("list2", functionForList2)

 function functionforList1(list){
    // do stuff
 }

 function functionforList2(list){
    // do *other* stuff
 }

function getList(listName, goToFunction) {

    var get = function (callback) {

        // blahblahblah

        currentcontext.executeQueryAsync(function () {
            callback(null, listItemCollection);

        }, function (a, b) {
            callback(new Error(b.get_message()));
        });
    };

    get(function (error, listItemCollection) {
        if (error) {
            alert(console.error(error));
            return;
        }

        goToFunction(listItemCollection);
    });
}     

Essentially now I can do:

    $(document).ready(function () {
        getList("mySPList", sharePointReady);
    });

    function sharePointReady(list) {
       // carry on having sorta asynchronously loaded it
       // and now I can use one function for each page
       // without like 5 global variables
    }

2 Answers 2

3

Sounds like you need promises which are in many ways the JavaScript equivalent of the Task class in .NET

Using jQuery promises your getList code would look something like this.

function getList(listName) {

    var def = new jQuery.Deferred();

    var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));

    var currentcontext = new SP.ClientContext.get_current();
    var hostcontext = new SP.AppContextSite(currentcontext, hostUrl);
    var web = hostcontext.get_web(); // hostcontext instead of currentcontext

    var lists = web.get_lists();
    var list = lists.getByTitle(listName);

    listItemCollection = list.getItems(""); // use CAML query for specific requests

    currentcontext.load(listItemCollection);

    currentcontext.executeQueryAsync(function () {
        def.resolve(listItemCollection);
    }, function (a, b) {
        def.reject(b.get_message());
    });

    return def.promise();
}

The key object is the jQuery.Deferred. I'm not going to go into the details of how Deferred objects and promises work here, there's lots of information on this available on the Internet. The important thing is that the use of Deferred objects and promises facilitate making asynchronous method calls.

The calling code would look like this:

var call = jQuery.when(getList("list1"), getList("list2"));
call.done(function (listItemCollection1, listItemCollection2) {
    doSomething(listItemCollection1);
    doSomething(listItemCollection2);
});
call.fail(function (errorMessage) {
    alert(errorMessage);
});
1
  • Thanks a bunch. This is a really interesting way of doing it that I've never heard of before. Commented Mar 5, 2015 at 16:06
2

Since SharePoint JSOM is async by its nature, i would recommend you to consider the following approaches.

Callback approach

The following example demonstrates how to perform a chained requests for loading of list items using JSOM:

function getListItems(listTitle,success,error)
{
   var ctx = SP.ClientContext.get_current();
   var list = ctx.get_web().get_lists().getByTitle(listTitle);
   var items = list.getItems(SP.CamlQuery.createAllItemsQuery());
   ctx.load(items);
   ctx.executeQueryAsync(function() {
       success(items);
   },error);
}


function getData(listTitles,success,error,curIdx,result)
{
    var curIdx = curIdx || 0;
    var result = result || {};
    getListItems(listTitles[curIdx],
      function(items)
      {
          result[listTitles[curIdx]] = items.get_data();
          curIdx++;
          if(listTitles.length > curIdx){
              getData(listTitles,success,error,curIdx,result);    
          }
          else {
              success(result);
          }
      },
      error);
}    

Usage

getData(['Contacts','Tasks'],
   function(data)
   {
       //print lists info..
       for(var listTitle in data) {
           for(var i=0; i < data[listTitle].length; i++) {
               var item = data[listTitle][i];
               console.log(item.get_item('Title'));
           }
       }

   },
   logError);

function logError(sender,args){
   console.log(args.get_message());
}

Request batching

Since SharePoint JSOM supports Request Batching, you could take advantage of this technique for loading multiple lists as demonstrated below:

function getListsData(listTitles,success,error)
{
    var result = {};
    var ctx = SP.ClientContext.get_current();
    for(var i = 0; i < listTitles.length;i++){
       var list = ctx.get_web().get_lists().getByTitle(listTitles[i]);
       var items = list.getItems(SP.CamlQuery.createAllItemsQuery());
       ctx.load(items);   
       result[listTitles[i]] = items;
    }
    ctx.executeQueryAsync(function() {
       success(result);
    },error);
}

Usage

getListsData(['Contacts','Tasks'],
   function(data)
   {
       //print lists info..
       for(var listTitle in data) {
           var items = data[listTitle].get_data();
           for(var i=0; i < items.length; i++) {
               var item = items[i];
               console.log(item.get_item('Title'));
           }
       }

   },
   logError);

function logError(sender,args){
   console.log(args.get_message());
}
2
  • 1
    I like your answers man, really useful and with good examples. Continue the good work. :)
    – Asad Refai
    Commented Apr 26, 2018 at 7:03
  • Thanks! :) Feel a bit burned out with SharePoint community and switched to StackOverflow nowadays Commented Apr 26, 2018 at 7:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.