New Control Flow Libraries

20 Feb 2012 | By Alex Young | Tags node libraries async

There are a lot of control flow libraries for Node. Most libraries seek to wrap more complex patterns like promises with a simpler API, and others place a unique emphasis on a particular aspect of control flow. Some are the result of extracting generic functionality from another project, and others purport to be the next great ‘async micro-framework’.

Here are some interesting new control flow libraries that I’ve been looking at recently.

Nue

Nue (License: MIT, npm: nue) by Toshihiro Nakamura supports serial execution, nesting, error handling, and sharing data between functions through this.data:

var flow = require('nue').flow
  , fs = require('fs')
  , myFlow;

myFlow = flow(
  function(file1, file2) {
    this.data.file1 = file1;
    this.data.file2 = file2;
    fs.readFile(file1, 'utf8', this.async());
    fs.readFile(file2, 'utf8', this.async());
  },
  function(data1, data2) {
    this.next(data1 + data2);
  },
  function(data) {
    if (this.err) throw this.err;
    console.log(data);
    console.log(this.data.file1 ' and ' + this.data.file2 ' are concatenated.');
    this.next();
  }
);

myFlow('file1', 'file2');

I liked the way the author’s examples made each ‘flow’ a reusable function, rather than simply demonstrating that arbitrary asynchronous functions can be executed in series. The API for Nue uses this quite a lot – for example, this.async is used to accept the parameters for the next function and return a suitable callback.

The author has also written Mocha tests.

 Batch

Batch (License: MIT, npm: batch) by TJ Holowaychuk makes it easier to collect groups of results from asynchronous operations:

var Batch = require('batch')
  , batch = new Batch;

ids.forEach(function(id) {
  batch.push(function(done) {
    User.get(id, done);
  });
});

batch.end(function(err, users) {
  // `users` now has all of the users that were loaded
});

Rather than using a promise, or enhancing forEach, TJ has opted to use an event-based API that should be familiar to Node developers.

Cascade

Cascade (License: MIT, npm: cascade) by Scott Rabin allows nested callbacks to be flattened by passing an array of functions and their arguments to the cascade function:

// Standard code
fs.rename('/tmp/hello', '/tmp/world', function(err) {
  if (err) throw err;
  fs.stat('/tmp/world', function(err, stats) {
    if (err) throw err;
    console.log('stats: ' + JSON.stringify(stats));
  });
});

// Cascade
cascade('/tmp/hello', '/tmp/world',
  cascade.chain(fs.rename),
  cascade.raise(null, 2),
  fs.stat,
  cascade.raise,
  function(stats) {
     console.log('stats: ' + JSON.stringify(stats));
  }
);

The API has lots of helpers for working with arguments, like filter, join, and map:

cascade(1, 2, 3, 4, 5, 6,
  cascade.map(function(i) {
     return (i % 2 === 0 ? 'even' : 'odd');
  }),
  callout
);

// "callout" receives these arguments:
// 'odd', 'even', 'odd', 'even', 'odd', 'even'

The author has written detailed tests using Vows.

 Pattern

Pattern (License: Apache v2.0, npm: p) by Nuno Job uses patterns to manage asynchronous iteration:

var insert_all = require('p')(), _;

// Simulate an asynchronous operation
function insert_element(data, callback) {
  setTimeout(function() { callback(data); },
    Math.ceil(Math.random() * 100));
}

insert_all([], _, function stop(l,cb) { cb(); });
insert_all(_, _, function catchall(l, cb) {
  insert_element(l.shift(), function elem_cb(elem) {
    console.log(elem + ' inserted');
    insert_all(l, cb);
  });
});

insert_all([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 
  function done() { console.error('done'); });

This is by the same developer who made Clarinet, an evented SAX parser. There’s a blog post about the library here: Pattern Matching in JavaScript for Asynchronous Iteration.

Invoke

Invoke (License: MIT, npm: invoke) by Steve Lloyd combines chainable methods with an API that looks inspired by promises:

invoke(function(data, callback) {
  // Async operation
}).and(function(data, callback) {
  // Parallel operation
}).then(function(data, callback) {
  // Runs after the first two
}).rescue(function(err) {
  // Error handler
}).end(initialData, function(data) {
  // Done
});

Notice how .then implies serial execution, while .and is used for parallel invocations.

This library has some nodeunit tests, and a full example (in examples / simple.js).

More

As I was researching this post I built up metadata from GitHub and npm to figure out what the newest modules were. For modules that I didn’t cover, have a look at New Control Flow Libraries.


blog comments powered by Disqus