0

I'm getting all the subwebs from a site in my app in SharePoint with:

var getW = getWebs($q)
    .then(function (results) {
        console.log(results); // Object with results from the first execute.         
    });

function getWebs($q) {

    var deferred = $q.defer();

    //App context etc..

    web = appContextSite.get_web();
    subWebs = web.getSubwebsForCurrentUser(null);

    context.load(subWebs, 'Include(Url, Created, Title, Lists)');
    context.executeQueryAsync(Function.createDelegate(this, function () {

        enumSubWebs = subWebs.getEnumerator();
        var arraySubWebs = [];
        var list = [];

        while (enumSubWebs.moveNext()) {

            var subWeb = enumSubWebs.get_current(),
            subWebUrl = subWeb.get_url();

            var _list = subWeb.get_lists().getByTitle('Custom List'); 

            list.push({
                'listItem': _list.getItemById(1),
                'webTitle': subWeb.get_title()
            });

            context.load(list[list.length - 1].listItem);

            promises.push(list);
        }

        context.executeQueryAsync(Function.createDelegate(this, function () {
            for (var i = 0; i < list.length; i++) {
                console.log(list[i].listItem.get_fieldValue()['Title']);
            }
           // Not sure what to put here, because the promise is returned to my .then above before it enters here.
        }));
        Q.allSettled(promises).then(function () {
            deferred.resolve(list);
        });
    }));
    return deferred.promise;
};

Then I would like to get a list item by ID (in 'Custom list') from each web.

I'm not sure where I'm doing anything wrong, but the the webs (incl list array) is returned, but listItem doesn't seems to be executed. How should I use the promise to return everything after the last executeQuery?

Robin
  • 740
  • 3
  • 12
  • 30

2 Answers2

1

You are using the deferred antipattern. Every asynchronous action should get its own promise that represents the result, only then you can properly compose them. Currently you have something like an array of lists of objects, but it needs to become an array of promises to work with Q.allSettled.

Actually, you don't even need Q.allSettled, as you don't fire multiple queries at the same time but only a single one.

So lets build a helper function for the promise creation:

 function load(context, objs, args) {
     if (!Array.isArray(objs))
         context.load(objs, args);
     else
         for (var i=0; i<objs.length; i++)
             context.load(objs[i], args[i]);
     var deferred = $q.defer();
     context.executeQueryAsync(function(sender, args) {
         deferred.resolve(objs); // sender, args don't seem to be helpful
     }, function(sender, args) {
         deferred.reject(args);
     });
     return deferred.promise;
}

Now you can use it like this:

function getWebs($q) {
    //App context etc..
    var web = appContextSite.get_web();
    var subWebs = web.getSubwebsForCurrentUser(null);

    return load(context, subWebs, 'Include(Url, Created, Title, Lists)')
//  ^^^^^^
    .then(function(loadedSubWebs) {
        var enumSubWebs = loadedSubWebs.getEnumerator();
        var list = [],
            loadList = [];

        while (enumSubWebs.moveNext()) {
            var subWeb = enumSubWebs.get_current(),
                subWebUrl = subWeb.get_url();

            var obj = {
                listItem: subWeb.get_lists().getByTitle('Custom List').getItemById(1),
                webTitle: subWeb.get_title()
            };
            list.push(obj);
            loadList.push(obj.listItem);
        }
        return load(context, loadList).then(function(loadedList) {
//      ^^^^^^
            for (var i = 0; i < loadedList.length; i++) {
                console.log(loadedList[i].get_fieldValue().Title);
            }
            return list;
        });
    }, function(err) {
        // the load failed. `err` will be the `args` passed to `reject`
        console.log("list doesn't exist in this web");
        // you can still resolve the promise:
        return [];
        // or alternatively rethrow the exception:
        throw err;
    });
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks! How will this react if a list doesn't exist? Will it break or continue the batch and return the obj? – Robin Feb 22 '15 at 17:37
  • @RangeRover: I don't know sharepoint, best try it out how `executeQueryAsync` behaves. The promises alone will not break, they will just resolve with the empty list. – Bergi Feb 22 '15 at 19:05
  • This never gets returned to .then in getWebs if the list doesn't exist. It works if the list exists. It runs the deferred.reject in executeQ in the load function. – Robin Feb 23 '15 at 09:10
  • @RangeRover: OK, but you expect it to return that empty list? Then use an extra condition for that case. – Bergi Feb 23 '15 at 09:20
  • If the list doesn't exist, I want it to return the web but without a list or just console.log for that web that the list doesn't exist under that web. Should I do this in the load function you mean? – Robin Feb 23 '15 at 09:24
  • No, `load` always would receive a list (even if its empty). If you want to do something about non-existing lists, and return completely different things, you should do that in the `getWebs` function. – Bergi Feb 23 '15 at 09:26
  • But I cannot check if the LIST exist before the executeQuery is runned (in the load function)? – Robin Feb 23 '15 at 09:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/71470/discussion-between-rangerover-and-bergi). – Robin Feb 23 '15 at 09:31
0

Here's something very similar to Bergi's answer but independently derived.

The main difference is that I have left the context.load() statements behind in the main routine and promisified just context.executeQueryAsync(). This is less rational than Bergi's load() but makes for a simpler helper and simpler main routine.

var getW = getWebs($q).then(function (results) {
    console.log(results); // log the `list` derived from the first execute.         
});

function executeQueryPromisified(context) {
    /* Ref 1 */
    var deferred = $q.defer();
    context.executeQueryAsync(
        function(sender, args) {
            deferred.resolve(args);
        },
        function(sender, args) {
            deferred.reject(args);
        }
    );
    return deferred.promise;
};

function getWebs($q) {
    var web = appContextSite.get_web(),
        subWebs = web.getSubwebsForCurrentUser(null);
    context.load(subWebs, 'Include(Url, Created, Title, Lists)');
    return executeQueryPromisified(context).then(function(args) {
        var enumSubWebs = subWebs.getEnumerator(),
            list = [], subWeb, obj;
        while (enumSubWebs.moveNext()) {
            subWeb = enumSubWebs.get_current();
            obj = {
                'listItem': subWeb.get_lists().getByTitle('Custom List').getItemById(1),
                'webTitle': subWeb.get_title()
            };
            list.push(obj);
            context.load(obj.listItem);
        }
        return executeQueryPromisified(context).then(function() {
            for (var i = 0; i < list.length; i++) {
                console.log(list[i].listItem.get_fieldValue()['Title']);
            }
            return list;
        });
    }).catch(function (args) {
        /* Ref 1 */
        console.error('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    });
};

Ref 1: Sharepoint documentation; SP.ClientContext.executeQueryAsync method

Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • Thanks! How will this react if a list doesn't exist? Will it break or continue the batch and return the obj? – Robin Feb 22 '15 at 17:38
  • That depends on the failure mode(s) of `.executeQueryAsync()`, which I'm not familiar with. In any case, it depends what your app wants to do under failure conditions. If you were to `return [];` from the `.catch()` clause, then that would be equivalent to a "successful" but empty list. If you were to `throw [];` from the `.catch()` clause, then that would be equivalent to a "unsuccessful" empty list. Suggest you try provoking different types of failure, see what happens then devise recovery strategy(s). – Roamer-1888 Feb 22 '15 at 19:48