Friday, May 18, 2012

Async VI: JavaScript Promises

The previous posts (Async basics, Exception handling, Cancellation, Progress reporting, void methods) all talked about async from a C# point of view. But let's not forget there is another very important language coming to WinRT and that's JavaScript in combination with HTML5. The cool thing is you can use more or less the same async capabilities here that you find in C#. Big difference is the syntax you will be using. Since the async syntax will be closer to what you are used to in JavaScript.

The UI in the JavaScript BlitzHiker application looks pretty much the same as its C# counterpart. It also has a page to publish a hikerequest, which does three big things, initialize the Bing map we use, find my own location and publish my hikerequest.

function initialize() {
    initializeBing();
    findMe();
    publish();
}

The findMe function has the first bit of asynchrony.

function findMe() {
    if (myPosition) {
        setMePushpin(myPosition)
    }
    else {
        if (!loc) {
            loc = Windows.Devices.Geolocation.Geolocator();
        }
        if (loc) {
            loc.getGeopositionAsync()
            .then(setMePushpin, errorHandler);
        }
    }
}

The call to GetGeopositionAsync is an asynchronous call. We can subscribe a callback to this method by using the then function. So no special keywords like await and async here, but more JavaScript style callbacks. You also see me using an error handler. In the JavaScript case, it is not with try catch that you handle exceptions, but with an extra error handler function you can send along.

I also made use in this function of the loc variable, that I defined globally. My original version of this method looked like this:

function findMe() {
    if (myPosition) {
        setMePushpin(myPosition)
    }
    else {
        Windows.Devices.Geolocation.Geolocator()
            .getGeopositionAsync()
            .then(setMePushpin, errorHandler);
    }
}

But funny enough only one times out of 10 the pushpin for my position was set correctly. This had me puzzled. Apparently, my local Geolocator had already gone out of scope before the callback came in. That is why I altered this to a global variable.

Another place where I used asynchronous callbacks, is in the publish method, which publishes a hikerequest, gets all the drivers and shows those on the Bing map.

function publish() {
    var caller = new BlitzHikerLib.RestCaller();
    caller.publishHikeRequestAsync()
    .then(function () {
        caller.getDriversAsync()
        .then(function (driverIds) {
            if (driverIds == null || driverIds.length == 0)
                return;
            for (var i = 0 ; i < driverIds.length && i < 200 ; i++) {
                var driverId = driverIds[i];
                caller.getUserAsync(driverId)
                .then(showDriver);
            }
        });
    });
}

You see me use then three times, each time sending a callback function along. Now, in my humble opinion, this syntax doesn't make this code the most readable code in the world. You need to look at it a couple of times to see what's going on. Now, you can actually improve on this by use of a little trick.

function publish() {
    var caller = new BlitzHikerLib.RestCaller();
    cancellable =
        caller.publishHikeRequestAsync()
        .then(function () {
            return caller.getDriversAsync()
        })
        .then(function (driverIds) {
            if (driverIds == null || driverIds.length == 0)
                return;
            for (var i = 0 ; i < driverIds.length && i < 200 ; i++) {
                var driverId = driverIds[i];
                caller.getUserAsync(driverId)
                .then(showDriver);
            }
        })
        .then(null, errorHandler);
}

You can see that for all but one, all then statements are nicely aligned. This can be done since we return the actual asynchronous call from the callback.

The reason I don't use this trick with GetUserAsync, is that, if I were to do that, I would jump out of the for loop that's processing each driver and only the first driver would be shown on the map.

The last then statement is special as well, since it defines a global error handler. Whatever error occurs in whatever callback, this error handler will catch it. Another useful trick.

I also extended this method a bit and actually caught the asynchronous calls in a variable, I called cancellable. Cancellation in JavaScript as well is done a bit differently. Once you define a global variable like this, you can call cancel on it.

function cancelHike() {
    if (cancellable != null) {
        cancellable.cancel();
        var errorDiv = document.getElementById("errorText");
        errorDiv.innerText = "operation cancelled";
    }
}

With this you have more or less the exact same application as I showed in the C# examples. But I do have to say I did need to alter some things in the RestCaller to get all this working for JavaScript. That is something I'll show you in the next (and last) blog post.

No comments:

Post a Comment