Async patterns

For JavaScript and Ruby

Michiel Kalkman

(use Chrome or Safari and the arrow keys)

Closures

In computer science, a closure is a first-class function with free variables that are bound in the lexical environment. Such a function is said to be "closed over" its free variables.
The Standard Repository of All Knowlegde and Wisdom (AKA Wikipedia)

Lexical environment is SCOPE

(Stuff you can shoot at!)

Closures example

Scoping rules for closures in JavaScript and Ruby are almost identical

(Aside from the JS mess with 'this')

The Good Old Days


function setupView() {
  var obj = getDBObject();
  var form = getForm();
  renderView(obj, form);
}

Ye Olde Invocation Chain

Optimistic nesting


function setupView() {
  getDBObject(function(obj) {
      handle(obj);
      getForm(function(form) {
          handle(form);
          renderForm();
          console.info("All done");
        });
    });
}

.. the end result


asyncCall(function() {
    asyncCall(function() {
        asyncCall(function() {
            asyncCall(function() {
                asyncCall(function() {
                    asyncCall(function() {
                        asyncCall(function() {
                          console.info("Mommy, what's my scope?");
                          });
                      });
                  });
              });
          });
      });
  });

[..] if your indentation goes too far to the right, then it means your function is designed badly and you should split it to make it more modular or re-think it.
Linus Torvalds, Linux Kernel Style Guide

Being 'smart'


function setupView() {
  getDBObject(handleDBObject);
}

function getDBObject(callback) {
  asyncCall(callback);
}

function handleDBObject(dbobj) {
  handle(dbobj);
  asyncCall(handleForm);
}

function handleForm(form) {
  handle(form);
  renderTheForm();
  console.info("All done");
}

The Newfangled Async Hell Pattern

What is a sequencer pattern?

Sequencer pattern


function getDBObject(callback) {
  asyncCall(function(dbobj) {
      handle(dbobj);
      callback();
    });
}

function getForm(callback) {
  asyncCall(function(form) {
      handle(form);
      callback();
    });
}

function renderForm(callback) {
  renderTheForm();
  callback();
}

sequence(
  getDBObject,
  getForm,
  renderForm
);

The Sequencer Pattern

What is a sequencer?

JavaScript Ruby

Sequencer = function(lambdas) {

  var self    = this;

  this.start = function() {
    this.next();
  }

  this.next = function() {
    if (lambdas.length != 0) {
      lambdas.shift()(function() {
        self.next();
      });
    }
  }

}
        

class Sequencer

  def initialize(lambdas=[])
    @lambdas = lambdas
  end

  def start
    self.next
  end

  def next
    if @lambdas.size > 0
      @lambdas.shift.call(
        lambda { self.next }
      )
    end
  end

end
        

What is a sequencer?

JavaScript Example use

Sequencer = function(lambdas) {

  var self    = this;

  this.start = function() {
    this.next();
  }

  this.next = function() {
    if (lambdas.length != 0) {
      lambdas.shift()(function() {
        self.next();
      });
    }
  }

}
        

function callA(callback) {
  callback();
}
function callB(callback) {
  asyncCall(function() {
    callback();
  }
}

var sequence = new Sequencer([
  callA,
  callB
]);
sequence.start()
        

What is a sequencer?

Ruby Example use

class Sequencer

  def initialize(lambdas=[])
    @lambdas = lambdas
  end

  def start
    self.next
  end

  def next
    if @lambdas.size > 0
      @lambdas.shift.call(
        lambda { self.next }
      )
    end
  end

end
        

callA = lambda {|cb| 
  cb.call
}
callB = lambda {|cb| 
  SomeAsync.method ( lambda { 
      cb.call
    }
  )
}
Sequencer.new([callA, callB]).start
        

What is a sequencer?

Ruby Example use

class Sequencer

  def initialize(lambdas=[])
    @lambdas = lambdas
  end

  def start
    self.next
  end

  def next
    if @lambdas.size > 0
      @lambdas.shift.call(
        lambda { self.next }
      )
    end
  end

end
        

it "should run three actions" do

  acc = 0
  f = lambda { |cb|
    acc+=1
    cb.call
  }
  Sequencer.new([f,f,f]).start

  acc.should == 3

end
        

Sequencer pattern


sequence(
  getDBObject, // 350ms
  getForm,     // 200ms
  renderForm
);

//
// Total request time : 550ms
//

Collector pattern

Collector pattern

Sequencer Collector

sequence(
  getDBObject, // 350ms
  getForm,     // 200ms
  renderForm
);

//
// Total request time : 550ms
//
        

collect([
  getDBObject, // 350ms
  getForm      // 200ms
  ],
  renderForm
);

//
// Total request time : 350ms
//

        

And now ...

.. Errors!

Ye Olde Invocation Chain

Error handling?

That doesn't work with async

Errors have to

  • preferably not occur at all or
  • be handled locally and always continue or
  • be handled locally and invoke an error callback

Summary

Q?