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!

4 comments:

  1. It sounds like you're looking for something a lot like what Kris Zyp proposed on es-discuss earlier this month[1]. Dunno how much momentum it's going to get with the committee, but it has been discussed. Here's a quick snip of his proposal:

    """
    I believe that the overwhelming need that is continually and constantly
    expressed and felt in the JS community in terms of handling asynchronous
    activity is fundamentally a cry for top-down controlled single-frame
    continuations (obviously not always stated in such terms, but that is
    the effective need/solution). In terms of an actual code example,
    essentially what is desired is to be able to write functions like:

    element.onclick = function(){
    // suspend execution after doSomethingAsync() to wait for result
    var result = doSomethingAsync();
    // resume and do something else
    alert(result);
    };
    """

    [1] https://mail.mozilla.org/pipermail/es-discuss/2011-October/017538.html

    ReplyDelete
  2. Have you seen TameJS? It is yet another Javascript compiler that adds "await" and "defer" keywords to help linearize Javascript:

    http://tamejs.org/

    ReplyDelete
  3. I'm a bit late to this post, but I absolutely love and can't live anymore without Streamline.js:

    https://github.com/Sage/streamlinejs

    It does what you're asking for in the most elegant way possible that I can imagine. I use it in all my (Node.js) projects.

    Enjoy. =)

    ReplyDelete
  4. Generators (already available in FF) provide basically the same functionallity as await statement, with some clutter though... see task.js.
    On server side you may want to look at node-fibers, which brings cooperative multithreading to node (with some performance hit). Rihno would be other option with it's support for continuations.

    ReplyDelete