Aug 29, 2012

Introducing Barista Deferreds


One of the concepts that JavaScript developers are familiar with is the concept of deferreds.

jQuery implements deferreds in a number of places, most commonly used when invoking an asynchronous ajax call and the developer wishes for some behavior to be run when the operation is complete:

http://api.jquery.com/category/deferred-object/

Example: Since the jQuery.get method returns a jqXHR object, which is derived from a Deferred object, we can attach a success callback using the .done() method.

$.get("test.php").done(function() { 
  alert("$.get succeeded"); 
});

Yesterday, I introduced the concept of Deferreds to Barista. In Barista, since the javascript engine is tied to .Net we can utilize multi-threading. In Barista, creating a new deferred with a function as a constructor parameter will actually invoke that function on a new thread.

Since the executing script may potentially complete prior to an asynchronous task being completed, we also have a global function called ‘waitAll’ which will accept a single, or array, of deferreds and wait until all deferreds have completed.

Example: In a new thread, randomly wait for a period of time. When complete, set the value of ‘result’ to the number of milliseconds waited.

var result;

var deferred = new Deferred(function () {
        var value = Math.floor((Math.random() * 1000) + 1);
        delay(value);
        return value;
    }).done(function (value) {
                result  = value;
});

waitAll(deferred);

result;

//Returns the number of milliseconds delayed.

This makes multi-threaded programming using barista pretty easy – multi-threaded web traversals to gather security or report on lists/files, or menu permissions checking, or other scenarios are that much more performant since they’re multi-threaded.

As part of the deferred work, I’ve reworked the Ajax object to actually return a deferred object when { async = true } on the parameter object. This leads to some interesting scenarios:

Example: This script will recursively call itself to perform a traversal of all child webs in the context as a flattened array.

var result = new Array();
var calls = new Array();
var webs = sp.currentContext.web.getWebs();
webs.forEach(function (w) {
    result.push(w.url);
    var deferred = web.ajax(w.url + "/_vti_bin/OFS.SharePoint.Services.Rest/Barista.svc/eval?c=" + encodeURIComponent("/_layouts/OFS.SharePoint.Services.UnitTests/Content/Barista_RecursiveWebsRetrieval.js"), { useDefaultCredentials: true, async: true });
//Todo: make getting the url of the currently running script easier

    deferred.done(function (data) {
        if (data.length > 0) {
            data.forEach(function (url) {
                result.push(url);
            });
        }
    });
    calls.push(deferred);
});

waitAll(calls);

result;

//Example result ( I have just three randomly named webs off my root web in the root site – everything else is a site collection):

["http://ofsdev/hTkCFsUfIPhVyFa",
"http://ofsdev/okHKxJAHoSPjOHS",
"http://ofsdev/kKWEHVvsLmtsOCQ",
"http://ofsdev/hTkCFsUfIPhVyFa/deepweb"]

It’s worthwhile to discuss what could happen when this script is executed to examine the potential benefits.

  • The script will asynchronously wait for responses from all recursive calls
  • Since WCF allows for a thread to be automatically created to handle to a request, a new thread will be created when the child ajax request comes in (up until the max threads configured in wcf/iis)
  • Since each call to the service is a new session, a properly-configured load balanced environment would potentially round-robin the requests to other WFEs in the farm.

So, in essence, this script is not only multi-threaded in that each child web is processed in a new thread, but it also has the potential of delving out work to other servers in the farm such that each WFE is tasked with performing a portion of the total work – e.g. # of child webs/number of servers in the farm.

Well that’s pretty cool.

The deferred implementation in Barista has a subset of the methods that can be used with jQuery – I don’t have any of the ‘progress’ type functions, or cancellation. However, from what is implemented, I think this opens up a number of ‘advanced’ scenarios and makes creating multi-threaded tasks easy, and especially makes aggregation of data a really interesting scenario.

Next up: Logging.