Hello, I'm Nick Fitzgerald. This is my weblog. You can also check out my shared items from Reader, moments on Twitter, and code on GitHub. Feel free to contact me about whatever.
August 10th, 2010
Any halfway experienced JavaScripter is familiar with the setTimeout
function. It takes two arguments: a function, and a number n. It will delay
calling the function until n milliseconds (give or take a few) have
passed. The delay is performed asynchronously. This means that the rest of your
program will not halt while the timeout is ticking down.
SetTimeout provides a building block for many other useful patterns appearing
in typical JavaScript development. In this article, I will document a few that I
use frequently. I am interested in hearing what setTimeout patterns you have
found useful in the comments.
function async (fn) {
setTimeout(fn, 20);
}
Although, seemingly useless at a first glance, asynchronously delaying the execution of a block of code by only a very small amount of time is surprisingly useful. Suppose that you have the following code:
function getEmployeeInfo (name, callback) {
var info = getCachedEmployeeInfo(name);
if ( typeof info === "object" ) {
callback(info);
} else {
$.get("/employees", { name: name }, function (info) {
setCachedEmployeeInfo(name, info);
callback(info);
});
}
}
Because of the way caching is implemented, the callback function is sometimes
executed asynchornously, and sometimes it is not. This is a problem, and Oliver
Steele has written about it more extensively than I will.
The solution is to create uniform behavior whether the information is cached or not, by forcing all branches of logic to be asynchronous.
function getEmployeeInfo (name, callback) {
var info = getCachedEmployeeInfo(name);
if ( typeof info === "object" ) {
async(function () { callback(info); });
} else {
$.get("/employees", { name: name }, function (info) {
setCachedEmployeeInfo(name, info);
callback(info);
});
}
}
Asyc is also an excellent buidling block for further abstractions.
function sometimeWhen (test, then) {
async(function () {
if ( test() ) {
then();
} else {
async(arguments.callee);
}
});
}
It is often the case that you want to execute a bit of code if some condition
is met in the future. This is exactly the use case for sometimeWhen.
A representative example for sometimeWhen is testing for when some set of
asynchronous operations have all completed. In the following example, getUrl
performs a simple GET request, and getUrlsInBatch will take a list of URLs and
a callback to call once all the URLs' request data has been received.
function getUrlsInBatch (urls, callback) {
var results = [];
for (var i = 0; i < urls.length; i++) {
getUrl(urls[i], function (data) {
results.push(data);
});
}
// When the length of the results and urls are equal, that means every
// request has completed and we can call the callback with all of the
// requested data.
sometimeWhen(function () { return results.length === urls.length; },
function () {
callback(results);
});
}
function whenever (test, then) {
sometimeWhen(test, function () {
then();
sometimeWhen(test, arguments.callee);
});
}
The whenever function is similar to sometimeWhen function in that it
asynchronously polls a condition via the test function parameter. However,
unlike sometimeWhen, it will continue testing for the condition forever, instead
of stopping after the first time that test() returns truthy.
As is typically the case with the most interesting client side features, not all
browsers and browser releases support the onhashchange event. Fear not, this
doesn't present a significant threat to the savvy JavaScripter:
(function () {
var hash = window.location.hash;
whenever(function () { return window.location.hash !== hash; },
function () {
hash = window.location.hash;
if ( typeof window.onhashchange === "function" ) {
window.onhashchange();
}
});
}());
During the processing and iterarion of large collections, the browser can become
unresponsive. The processing and iteration code doesn't necessarily need to be
faster, though that is still a possibility, but more often than not the code
needs to yield control to the UI thread and let the browser become responsive
again. This is typically performed with recursive calls to setTimeout in
between iterations on the data set.
This pattern can be generalized in to the yieldingEach function. Items is
the data set we will be iterating over. IterFn is a function that will be
called on each item, similar to the function you might pass to
Array.prototype.forEach, but if it returns false then the iteration will
stop and the callback function will be called early. If iteration isn't halted
prematurely, callback is called after all the items have been processed. It is
passed the whole collection of items as it's only argument.
function yieldingEach (items, iterFn, callback) {
var i = 0, len = items.length;
async(function () {
var result;
// Process the items in batch for 50 ms, or while the result of
// calling `iterFn` on the current item is not false..
for ( var start = +new Date;
i < len && result !== false && ((+new Date) - start < 50);
i++ ) {
result = iterFn.call(items[i], items[i], i);
}
// When the 50ms is up, let the UI thread update by defering the
// rest of the iteration with `async`.
if ( i < len && result !== false ) {
async(arguments.callee);
} else {
callback(items);
}
});
}
YieldingMap is a specialization of yieldingEach where you might want to
perform the same transformation to each item in items and then do something
else with the resulting array. It is a yielding version of the
cross-browser-incompatible Array.prototype.map.
function yieldingMap (items, iterFn, callback) {
var results = [];
yieldingEach(items,
function (thing) {
results.push( iterFn.call(thing, thing) );
},
function () {
callback(results);
});
}
I know I have only touched on a corner of the large subject of the common patterns
that arise and use cases for setTimeout. I am interested in hearing yours as well.
Source Map Specification Discussion Mailing List on February 28th, 2013
Regarding "Dynamic Source Maps" on January 22nd, 2013
Update on Firefox and Source Maps on July 30th, 2012
Source Code Cartography on December 23rd, 2011
Pycco Needs a Loving Home on August 17th, 2011
Operational Transformation Part 2: Operations on April 5th, 2011
Operational Transformation: An Introduction on March 26th, 2011
JavaScript Timer Congestion on March 8th, 2011
OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance on December 31st, 2010
Pythonic JavaScript Methods on November 16th, 2010