Let's Make a Framework: Promises Part 4

Alex R. Young





tutorials frameworks lmaf documentation promises

Let's Make a Framework: Promises Part 4

Posted by Alex R. Young on .

tutorials frameworks lmaf documentation promises

Let's Make a Framework: Promises Part 4

Posted by Alex R. Young on .
*Let's Make a Framework* is an ongoing series about building a JavaScript framework from the ground up. These articles are tagged with [lmaf](http://dailyjs.com/tags.html#lmaf). The project we're creating is called [Turing](http://github.com/alexyoung/turing.js). Documentation is available at [turingjs.com](http://turingjs.com/).

Over the last three weeks I've been looking at how web frameworks
implement promises, and I've built a Promise class along
the way.

Ajax Integration

After researching promises, jQuery's API stood out as a solid example of
using them to do common tasks. In particular, jQuery.ajax
is Deferred-compatible, which means it transparently provides
promise-related methods:

  function() { alert('Success'); },
  function() { alert('Failure'); }

We should be able to recreate this with our turing.Promise
class, even though it's extremely simple. There are a few things to keep
in mind:

  • When implementing functions that support turing.Promise, they should return an object with a method that has the same signature as then(success, failure)
  • I designed Turing to be modular, so turing.Promise may not be available
  • The callee will return a promise that can be set with success/failure functions that are subsequently run later inside the original callee. It's not as mind-bending as it sounds!


I wrote a little Express app to test turing.net. I've
updated it to work with Express 2.x. This should provide a useful test
for promises:

'test promises': function() {
    function(r) { assert.equal('{"key":"value"}', r.responseText); },
    function(r) { assert.ok(false); }

The value in the assertion, '{key, is just
something I set the Express app to return. These tests can be run by
changing directory to test/functional then running
node ajax.js and visiting


The beginning of the net module's internal ajax function
needs to be changed to instantiate a Promise if it's

function ajax(url, options) {
  var request = xhr(),

  if (turing.Promise) {
    promise = new turing.Promise();

Now we've got one, what do we do with it? Well, we need to make it
accessible to the outside by changing whatever ajax()
returns to include a then method. This is the bare minimum.

This function returns an XMLHttpRequest object (or IE's
various equivalents). I just added a then method to this,
but it would probably be better to return something else and keep the
request object around internally.

request.then = function() {
  if (promise) promise.then.apply(promise, arguments);

I made then just defer to the current promise object, and
used apply to call it with arguments. If any
beginners find this puzzling, give
arguments and
apply a read on MDN. These language features are extremely useful for making
flexible APIs.

The final thing to do is actually call the success or failure functions
set up by then. They just need to go where the original
callbacks get executed. Recall that our Promise class uses
resolve(value) and reject(value) to handle the
resolution of a promise:

  function respondToReadyState(readyState) {
    if (request.readyState == 4) {
      if (request.getResponseHeader('content-type') === 'application/json')
        request.responseJSON = net.parseJSON(request.responseText);

      if (successfulRequest(request)) {
        if (options.success) options.success(request);

        // HERE:
        if (promise) promise.resolve(request);
      } else {
        if (options.error) options.error(request);

        // HERE:
        if (promise) promise.reject(request);


That wasn't too painful, was it? I think there may be a good argument
for making Promise part of Turing's core module,
particularly as it's so short and useful. By following this example,
promises could be used throughout the framework to enhance the API.

The latest code update was commit