JavaScript for C# developers: callbacks (part III)

In the previous two installments (one, two) we explored the use of callbacks through creating a mapp function for arrays (so called because the latest JavaScripts have a native map method already), and through creating a mapAsync function where the work is done asynchronously rather than serially. The reason for this was so that we could avoid triggering the browser’s “script running a long time” warning and, also, more importantly, provide the user with a responsive UI.

riskphoto © 2007 sputnik | more info (via: Wylio)In this final part I just want to tidy up the mapAsync method. When I left it last time,the function would process each element using a delay. This is, in all probability, way too much work and way too slow. We could potentially do more work each cycle without inconveniencing the user and thereby reduce the number of delays we have to go through. But how much work? How long do we have? Jakob Nielsen says that

0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.”

OK, then, 100 milliseconds it is. Actually, thinking about it, that leaves no time for maneuver and so I’ll go for half that for the mapAsync method.

So, what we’d like to do is process as many elements as we can in 50 milliseconds (instead of just processing the one) and then recurse using a delay for the next batch. We therefore need the ability to time our processing.

JavaScript comes with the Date class. If you new up a Date, what you get is a variable containing the current date/time, much as DateTime.Now does in C# and .NET. What we’ll do is get the start time and then start processing as many elements as possible. For each element we’ll process it, then get the current time. If the current time minus the start time is less than 50ms, round the loop we go again. Unfortunately, we can’t just subtract one Date from another, the minus operator only works for numbers. However all is not lost: if we convert a Date to a number, we get the number of milliseconds from some base Date. And to convert? Just use the unary plus operator. Here’s an example:

var start = +new Date();
do {
  // work
} while ((+new Date() - start) < 50);

We get the current date/time as the number of milliseconds by newing up a Date and then converting it to a number by use of the unary plus. We then enter a do..while loop, and continue round doing work while the current date/time minus the start time is less than 50. Pretty easy, no?

Incorporating it into the current version of mapAsync is fairly mundane:

Array.prototype.mapAsync = function (process, done, context) {
  var i = 0,
      result = [],
      last = this.length,
      self = this,
      processAsync = function () {
        var start = +new Date();
        do {
          if (i in self) {
            result[i] = process.call(context, self[i], i);
          }
          i++;
        } while ((i < last) && (+new Date() - start) < 50);
        console.log("*"); // just for showing progress
        if (i === last) {
          done.call(context, result);
        }
        else {
          processAsync.delay(10);
        }
      };
  processAsync();
};

Of course, there’s a bit more to that inner loop condition since we also want to break out if we manage to process the final element. I’ve thrown in a logging statement after the loop for now, just to show that we’re nicely chunking the work and still performing several delayed calls to processAsync. We’ll delete it when we’ve shown it working.

Of course, my little example of an 8-element array just isn’t going to cut the mustard with this new code, so let’s go for broke and use a 100,000 element array:

var myArray = [];
for (var i = 0; i < 100000; i++) {
  myArray[i] = i;
}

myArray.mapAsync(function (element, index) {
  return "<" + index.toString() + ": " + element.toString() + ">";
}, function (a) {
  console.log("done");
  delete myArray;
});

All the completion callback does this time is to log “done” (printing the array will overload the console – I know because I did it) and delete the original array – it is fairly big after all. When I run this code in Firebug, I get this:

*
undefined
*
*
*
*
*
*
*
*
*
done

Again, the “undefined” at the top is Firebug printing the return value of calling mapAsync. As you can see, it chunked the 100,000 element array into 10 delayed calls, roughly 10,000 elements per call. Or, if you like, I managed to process 10,000 elements in 50 milliseconds, which ain’t too shabby.

That’s it for this series on callbacks. I hope it proved useful. Remember: if you want to use this mapArray method (I use an MIT license, so go for it), do take out the logging statement in the middle.

(This JavaScript callback series: part I, part II, part III.)

Album cover for Year of the CatNow playing:
Stewart, Al - On the Border
(from Year of the Cat)


Loading similar posts...   Loading links to posts on similar topics...

No Responses

Feel free to add a comment...

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response