Showing posts with label asynchronous. Show all posts
Showing posts with label asynchronous. Show all posts

Wednesday, July 8, 2015

Async Made Easy

Asynchronous Code Doesn't Have to be Complicated
One of the best things about functional programming with Node is how simple it can be to perform lots of potentially slow functions asynchronously.

That's also one of the worst things.

It's very easy to write really bad code or code that doesn't act the way you expect if you lose sight of the asynchronicity of it all. Fortunately, there are great utility packages like async that make it simple to write good code that acts properly.

Take the following example. Let's say I have a web service that performs some function to its input and returns a new value. For test purposes, let's use the following Node+Express web application that has a single route which reverses a given string.

var express = require("express");
var app = express();

function reverse(s) {
    var o = "";
    for (var i = s.length - 1; i >= 0; i--) {
        o += s[i];
    }
    return o;
}

app.get("/reverse/:s", function(req, res, next) {

    res.setHeader("Content-Type", "text/plain");
    res.send(reverse(req.params.s));

});

// ------------ start listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

Now if I have another application that wants to call that reversal web service with 1000 strings and wait for them all to complete before moving on, what should I do? Making HTTP calls is an asynchronous operation. I make the call and then supply a callback function to let me know when it finishes. Here's the code that sets up the test: a function that makes the web service call and invokes a callback when complete, and an array of 1000 random test strings.

var http = require('http');

function makeString() {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for( var i=0; i < 25; i++ ) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
}

function reverseIt(str, callback) {

    return http.get({
        host: 'localhost',
        port: 3000,
        path: '/reverse/' + str
    }, function(response) {
        var body = '';
        response.on('data', function(d) {
            body += d;
        });
        response.on('end', function() {
            callback(body);
        });
    });

}

var testStrings = [];
for (var i = 0; i < 1000; i++) {
    testStrings.push(makeString());
}

First look at what is probably the most commonly used - and incorrect - way of making all the web service calls.

// ----- incorrect - no idea when they are all done

for (var j = 0; j < testStrings.length; j++) {

    reverseIt(testStrings[j], function(r) {
        console.log("got: " + r);
    });

}

console.log("finished!");

The problem is that you have no idea when all the calls complete. The console.log("finished!") is output almost immediately after you start the program, and the individual logs of the backwards strings keep coming out. Don't do this.

The thing that people do after realizing that first method fails is to use the callback from the completed web service invocation to call the web service again, and again, and again until all the strings are processed. Something like this:

// ----- does everything in series.
//       really slow, but you know when it's finished.

function rev(idx, callback) {
    reverseIt(testStrings[idx], function(r) {
        console.log("got: " + r);
        if (idx === testStrings.length - 1) {
            callback();
        } else {
            rev(idx+1, callback);
        }
    });
}

console.time("type2");
rev(0, function() {
    console.log("did them all!");
    console.timeEnd("type2");
});

Defining a wrapper function that makes the call to reverseIt and then calls itself in reverseIt's callback does allow you to know when all 1000 calls have completed. But doing one request at a time invalidates all the performance gains made possible by the asynchronous nature of the http calls. The console.time and console.timeEnd functions will be used as proof of how this is the worst of the "correct" methods. In my local tests, the complete set of 1000 string reversals took on average around 675 milliseconds.

What is the better way? We can still make all the web service calls asynchronously if we set up some way of tracking their completion. Something like this:

// ----- better

function revManager(arr, callback) {

    var counter = 0;
    return {
        go: function() {
            for (var i = 0; i < arr.length; i++) {
                reverseIt(arr[i], function(r) {
                    console.log("got: " + r);
                    counter++;
                    if (counter === arr.length) {
                        callback();
                    }
                });
            }
        }
    };

}

console.time("type3");
revManager(testStrings, function() {
    console.log("all done!");
    console.timeEnd("type3");
}).go();

This method creates a manager for reversing all the strings that will only invoke a callback when all have completed. To keep track of how many have finished, it sets up a closure with a counter variable and returns an object containing a function to start the process - the go function on line 7. It loops over the array of strings and calls reverseIt on each one, much like in the incorrect example above. But in this callback function for reverseIt, the counter variable is incremented and then checked to determine if all have finished. If they have, the manager's callback is invoked. That's the point when we are sure that all 1000 have completed. The timing on this method proves that it completes much faster - my tests averaged 203 milliseconds.

But way back up near the top I mentioned how utility packages like async make this easier. So now that I've done it the hard way, take a look at how async makes it easy with a general-purpose each function:

// ----- easy with async

var async = require('async');

console.time("type4");
async.each(testStrings, function(item, cb) {
    reverseIt(item, function(r) {
        console.log("got: " + r);
        cb();
    });
}, function() {
    console.log("complete!");
    console.timeEnd("type4");
});

Async has a bunch of methods for different patterns, but the each function corresponds to the desired behavior in this example. It takes three parameters. The first is the array of items, and second is what they call the iterator function. This function is called and passed each item as well as a callback. The general contract is that the iterator function should do its thing and then call the callback when complete. The third parameter to each is the callback function for when the entire array has been processed.

The timing indicates that async's each function performs exactly the same as my own function, but is much more generic and allows me to write fewer lines of code. That's a win-win!

Follow my example and use the time you save by writing less code to enjoy today's comic!

Amphibian.com comic for 8 July, 2015

Wednesday, September 24, 2014

It's the Age of Asynchronous

I'm not sure how many people have noticed this yet, but Firefox 30 deprecated synchronous XHR on the main thread. They said that it leads to a degraded user experience, and they're correct - in most cases. But I've used it on occasion without any serious side effects, I just had to be careful about what I was doing.

Still, I should probably stop doing it. How can I call synchronous XHR Ajax if the A in Ajax stands for Asynchronous? Take away the A and it's just Jax. Not cool. Of course, the X stands for XML and most people use JSON instead these days. But what is Ajaj? That doesn't even make any sense! Let's move on...

A common use of the synchronous XHR is to ensure order of operations. If you have to make calls to get data from 2 URLs before doing some kind of processing, you can't guarantee which one will return first. If you can't use synchronous XHR anymore, you could stack up the calls inside callbacks so you only ever do one at a time...but then you're still only doing one at a time. If one call takes 3 seconds and the other takes 2, you have a total wait time of 5 seconds before you can continue. If you could somehow call both at once but still make sure the results are processed in a certain order, your total wait time is only 3 seconds. Saving 2 seconds might not sound like much, but it can really add up. And when you have a human being waiting for some on-screen response, 2 seconds is an eternity. Plus, performing the calls sequentially in callbacks might be fine for 2 or 3 calls, but it gets crazy when you need to stack 5 or 6 callbacks deep.

There must be a better way.

There is! If you use jQuery, the when().done() functions can handle the difficult task of making multiple asynchronous calls and keeping the responses in order.

The when() function takes as arguments as many Deferred or Promise objects as you want to pass it. When all of them have completed, the function passed to done() is called with the results of the Deferreds as arguments in the order they were given to when().

Since Ajax calls return Promises in jQuery, they can be used as arguments to when(). For Ajax Promises, the results passed to the function given to done() will be 3-element arrays. The first element in the array will be the returned data, the second will be the status text, and the third will be the jqXHR object. I've thrown together a working example of this...

Here is my test HTML page. The JavaScript starting on line 20 is the important part.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Async Order Test</title>
</head>

<body>

  <p>when().done() test page</p>

</body>

<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script>

$(function() {

    $.when(

        $.get('something/a'),
        $.get('something/b')

    ).done(function(a, b) {

        console.log(a);
        console.log(b);

    });

});

</script>

</html>

And here is my Node/Express server app.

var express = require("express");
var app = express();

app.get("/something/a", function(req, res, next) {
    // simulate a long response time for this path.
    // we won't send the response for 3 seconds.
    setTimeout(function() {
        res.status(200).send("this is a");
    }, 3000);
});

app.get("/something/b", function(req, res, next) {
    res.status(200).send("this is b");
});

// ------------ static content
app.use(express.static("public"));

// ------------ start the listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

I've set up the /something/a resource to take a long time to respond. The /something/b resource should respond as quickly as possible. Therefore "b" should pretty much always return first even though it is second in the list passed in to when(). Looking at the console output, it is clear that the "a" response is printed out first despite returning last.


The when() function can be used for other types of Deferreds as well - it is not limited to Ajax calls although these are perhaps the most common uses.

If you're not using jQuery (perhaps you have a similar issue on the server side in Node), there are other libraries available that provide similar functionality. Check out Async.js for a good one. If you are a hardcore computer science fanatic, you could implement something yourself. Here is an example I quickly threw together for doing it in Node...

var http = require('http');

function waitFor(urls, cb) {

    var countDown = urls.length;
    var retVals = new Array(countDown);

    function processResponse(idx) {
        return function(response) {
            var str = '';
            response.on('data', function(chunk) {
                str += chunk;
            });
            response.on('end', function() {
                fillInData(idx, str);
            });
        };
    }

    function fillInData(slot, data) {
        retVals[slot] = data;
        countDown--;
        if (countDown === 0) {
            cb(retVals);
        }
    }

    for (var i = 0; i < countDown; i++) {
        http.get(urls[i], processResponse(i));
    }

}

var urls = [ 'http://localhost:3000/something/a',
             'http://localhost:3000/something/b' ];

waitFor(urls, function(results) {
    console.log(results);
});

My example is not quite as flexible as what jQuery or Async.js offer, but you can see the basics of how it works. Given an array of URLs and a callback, my waitFor function will request data from all the URLs asynchronously but only call the callback when all the requests have completed. The array of data passed to the callback will be in the same order as the URLs were given. It works using a countdown latch mechanism. For each response received, the countdown in decremented. If the countdown reaches 0, all responses have come in and the callback can be called. The callback is passed an array that has been populated with each response's data. The data went in the proper array index because the index of the URL was passed along to to the function that handled the response. The only unusual thing is how the processResponse callback is given to the http.get function. Calling processResponse and passing an index actually returns an other function which is the real callback for http.get. The reason for this is to create a new closure for the index so that the response callbacks are not affected by the for loop continuing to run and incrementing the index. Without that extra function, the data from all responses almost always goes into the same position, 2, in the retVals array.

Working with lots of asynchronous functions can be confusing and frustrating sometimes. A good library like jQuery can help make it less painful.

Amphibian.com comic for September 24, 2014