Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimal/preferred way to call 'SP.ClientContext.executeQueryAsync' in SharePoint

I have been learning client-side object model and came across the method executeQueryAsync. I found there are quite a few ways to call this method. Some of the one I found were these:

var context = new SP.ClientContext.get_current();

// Option 1
context.executeQueryAsync(
    function(sender, args){ },
    function(sender, args){ }
);

// Option 2
context.executeQueryAsync(
    Function.createDelegate(this, _onSucceed), 
    Function.createDelegate(this, _onFail)
);

// Option 3
context.executeQueryAsync(
    Function.createDelegate(this, this._onSucceed), 
    Function.createDelegate(this, this._onFail)
);

// Option 4
context.executeQueryAsync(_onSucceed, _onFail);

Which of this way is the most optimal/preferred one? Also what does the statement Function.createDelegate do? The documentation for this function seems to be very cryptic for me.

like image 284
Naveen Avatar asked Jul 21 '14 16:07

Naveen


1 Answers

First I would say there is no 'optimal way' as these all just behave somewhat differently... Second, I would add this isn't so much a SharePoint or executeQueryAsync specific thing as it is a JS thing in general...

Next we need to understand that executeQueryAsync expects two functions as arguments: the first is a function to perform if executeQueryAsync succeeds, the second is a function to perform if the method encounters an error. These functions are passed parameters (from executeQueryAsync, not from your JS) representing the sending object as well as an arguments object that can have some data (args.get_message() and args.get_stackTrace() are common in the case of a failed call)

In your 'Option 1' example, executeQueryAsync is given two anonymous functions, you won't be able to re-use them anywhere, but if the behavior is simple this may be sufficient.

In Option 2 you use the createDelegate method to give the success and failure callbacks a context -- this speaks to scoping within JavaScript; if you need to reference a variable that is only accessible in the function that calls executeQueryAsync, you'll need to use this sort of pattern so that this within the callback references the function that called executeQueryAsync instead of the success or failure function that you're now in. You can think of creating a delegate as the calling function calling on some other function, but saying 'I want that function to be able to see what I can see no matter where it's located at within the code.' This may all seem a bit arcane, but such is scoping within JavaScript... You could completely circumvent the need for doing this by referencing variables at higher scope levels (say inside of a function that contains the calling method as well as the success and failure methods)

Option 3 is just like Option 2, except it just specifies that the _onSucceed or _onFail functions should be the ones that are contained within the calling object

Option4 is just like Option 1, except that you've named the functions (and that they are available within the current scope) and are calling them by name.

I usually use something like option 2, or option 4 -- but I hope you can see that it really just depends on how you're trying to structure your code.

EDIT: In response to a comment about Function.createDelagate() -- It seems to just be a helper in an ASP.NET script resource; it does nothing other than calling apply() (which is the standard JS way of doing this -- see MDN documentation here). It might also provide some backward compatibility somewhere within ASP.NET, but I'm not really sure!

Here is the code for the function from a script resource file in my SP environment:

Function.createDelegate = function(a, b) {
    return function() {
        return b.apply(a, arguments)
    }
};

And as a bonus, I was thinking about how I use executeQueryAsync and I realized that I actually use it more often like option 1, with a promise pattern using jQuery deferreds like this:

function getSPDataAsync(context) {
    var deferred = $.Deferred();
    context.executeQueryAsync(function(sender, args) {
        deferred.resolve(sender, args);
    }, function(sender, args) {
        deferred.reject(sender, args);
    });
    return deferred.promise();
}

Then you can do things a little less-spaghetti-like, such as:

...
ctx.load(items);
getSPDataAsync(ctx).then(function() {
    //do some stuff with the data when the promise resolves
});

Just in case anyone cares! :)

like image 178
John-M Avatar answered Oct 26 '22 20:10

John-M