Friday, October 28, 2011

I want a magical operator to assuage my async woes (and a pony)

Lately, I have spent a lot of time thinking about how I could reduce the tedium of async programming in JavaScript. For example, consider a typical implementation of using an XMLHttpRequest to do a GET that returns a Deferred (this example uses jQuery's implementation of Deferred, but there are many other reasonable implementations, and there is a great need to settle on a standard API, but that is a subject for another post):
/** @return {Deferred} */
var simpleGet = function(url) {
  var deferred = new $.Deferred();

  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200) {
        deferred.resolve(xhr.responseText);
      } else {
        deferred.reject(xhr.status);
      }
    }
  };
  xhr.open('GET', url, true /* async */);
  xhr.send(null);  

  return deferred;
};
What I want is a magical ~ operator that requires (and understands) an object that implements a well-defined Deferred contract so I can write my code in a linear fashion:
/** @return {Deferred} */
var getTitle = function(url) {
  if (url.substring(0, 7) != 'http://') url = 'http://' + url;
  var html = ~simpleGet(url);
  var title = html.match(/<title>(.*)<\/title>/)[1];
  return title;
};

/** Completes asynchronously, but does not return a value. */
var logTitle = function(url) {
  try {
    var title = ~getTitle(url);
    console.log(title);
  } catch (e) {
    console.log('Could not extract title from ' + url);
  }
};
Unfortunately, to get this type of behavior today, I have to write something like the following (and even then, I am not sure whether the error handling is quite right):
/** @return {Deferred} */
var getTitle = function(url) {
  if (url.substring(0, 7) != 'http://') url = 'http://' + url;

  var deferred = new $.Deferred();
  
  simpleGet(url).then(function(html) {
    var title = html.match(/<title>(.*)<\/title>/)[1];
    deferred.resolve(title);
  }, function(error) {
    deferred.reject(error);
  });

  return deferred;
};

/** Completes asynchronously, but does not return a value. */
var logTitle = function(url) {
  var deferred = getTitle(url);
  deferred.then(function(title) {
    console.log(title);
  }, function(error) {
    console.log('Could not extract title from ' + url);
  })
};
I am curious how difficult it would be to programmatically translate the first into the second. I spent some time playing with generators in Firefox, but I could not seem to figure out how to emulate my desired behavior. I also spent some time looking at the ECMAScript wiki, but it is unclear whether they are talking about exactly the same thing.

In terms of modern alternatives, it appears that C#'s await and async keywords are the closest thing to what I want right now. Unfortunately, I want to end up with succinct JavaScript that runs in the browser, so I'm hoping that either CoffeeScript or Dart will solve this problem, unless the ECMAScript committee gets to it first!

Please feel free to add pointers to related resources in the comments. There is a lot out there to read these days (the Dart mailing list alone is fairly overwhelming), so there's a good chance that there is something important that I have missed.

Update (Fri Oct 28, 6:15pm): I might be able to achieve what I want using deferred functions in Traceur. Apparently I should have been looking at the deferred functions strawman proposal more closely: I skimmed it and assumed it was only about defining a Deferred API.

Want to learn about a suite of tools to help manage a large JavaScript codebase? Pick up a copy of my new book, Closure: The Definitive Guide (O'Reilly), and learn how to build sophisticated web applications like Gmail and Google Maps!