Chainable asynchronous flow control for node.js with sequential and parallel primitives and pipeline-style error handling
JavaScript CoffeeScript
Switch branches/tags
Nothing to show
Pull request Compare This branch is 27 commits behind substack:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
examples
test
README.markdown
index.js
package.json

README.markdown

Seq

Seq is an asynchronous flow control library with a chainable interface for sequential and parallel actions. Even the error handling is chainable.

Each action in the chain operates on a stack of values. There is also a variables hash for storing values by name.

Examples

stat_all.js

var fs = require('fs');
var Hash = require('hashish');
var Seq = require('seq');

Seq()
    .seq(function () {
        fs.readdir(__dirname, this);
    })
    .flatten()
    .parEach(function (file) {
        fs.stat(__dirname + '/' + file, this.into(file));
    })
    .seq(function () {
        var sizes = Hash.map(this.vars, function (s) { return s.size })
        console.dir(sizes);
    })
;

Output:

{ 'stat_all.js': 404, 'parseq.js': 464 }

parseq.js

var fs = require('fs');
var exec = require('child_process').exec;

var Seq = require('seq');
Seq()
    .seq(function () {
        exec('whoami', this)
    })
    .par(function (who) {
        exec('groups ' + who, this);
    })
    .par(function (who) {
        fs.readFile(__filename, 'ascii', this);
    })
    .seq(function (groups, src) {
        console.log('Groups: ' + groups.trim());
        console.log('This file has ' + src.length + ' bytes');
    })
;

Output:

Groups: substack : substack dialout cdrom floppy audio src video plugdev games netdev fuse www
This file has 464 bytes

Methods

Each method executes callbacks with a context (its this) described in the next section. Every method returns this.

Whenever this() is called with a non-falsy first argument, the error value propagates down to the first catch it sees, skipping over all actions in between. There is an implicit catch at the end of all chains that prints the error stack if available and otherwise just prints the error.

Seq()

Seq(x, y...)

The constructor function creates a new Seq chain with the methods described below. The optional arguments given become the new context stack.

Seq.ap([x, y...])

Exactly like Seq(x, y...) or Seq().extend([x,y...]). This is just another handy way for populating the stack. The ap is short for apply.

seq(cb)

seq(key, cb, *args)

This eponymous function executes actions sequentially. Once all running parallel actions are finished executing, the supplied callback is apply()'d with the context stack.

To execute the next action in the chain, call this(). The first argument must be the error value. The rest of the values will become the stack for the next action in the chain and are also available at this.args.

If key is specified, the second argument sent to this goes to this.vars[key] in addition to the stack and this.args. this.vars persists across all requests unless it is overwritten.

All arguments after cb will be bound to cb, which is useful because .bind() makes you set this. If you pass in Seq in the arguments list, it'll get transformed into this so that you can do:

Seq()
    .seq(fs.readdir, __dirname, Seq)
    .seq(function (files) { console.dir(files) })
;

which prints an array of files in __dirname.

par(cb)

par(key, cb, *args)

Use par to execute actions in parallel. Chain multiple parallel actions together and collect all the responses on the stack with a sequential operation like seq.

Each par sets one element in the stack with the second argument to this() in the order in which it appears, so multiple pars can be chained together.

Like with seq, the first argument to this() should be the error value and the second will get pushed to the stack. Further arguments are available in this.args.

If key is specified, the result from the second argument send to this() goes to this.vars[key]. this.vars persists across all requests unless it is overwritten.

All arguments after cb will be bound to cb, which is useful because .bind() makes you set this. Like .seq(), you can pass along Seq in these bound arguments and it will get tranformed into this.

catch(cb)

Catch errors. Whenever a function calls this with a non-falsy first argument, the message propagates down the chain to the first catch it sees. The callback cb fires with the error object as its first argument and the key that the action that caused the error was populating, which may be undefined.

catch is a sequential action and further actions may appear after a catch in a chain. If the execution reaches a catch in a chain and no error has occured, the catch is skipped over.

For convenience, there is a default error handler at the end of all chains. This default error handler looks like this:

.catch(function (err) {
    console.error(err.stack ? err.stack : err)
})

forEach(cb)

Execute each action in the stack under the context of the chain object. forEach does not wait for any of the actions to finish and does not itself alter the stack, but the callback may alter the stack itself by modifying this.stack.

The callback is executed cb(x,i) where x is the element and i is the index.

forEach is a sequential operation like seq and won't run until all pending parallel requests yield results.

seqEach(cb)

Like forEach, call cb for each element on the stack, but unlike forEach, seqEach waits for the callback to yield with this before moving on to the next element in the stack.

The callback is executed cb(x,i) where x is the element and i is the index.

If this() is supplied non-falsy error, the error propagates downward but any other arguments are ignored. seqEach does not modify the stack itself.

parEach(cb)

parEach(limit, cb)

Like forEach, calls cb for each element in the stack and doesn't wait for the callback to yield a result with this() before moving on to the next iteration. Unlike forEach, parEach waits for all actions to call this() before moving along to the next action in the chain.

The callback is executed cb(x,i) where x is the element and i is the index.

parEach does not modify the stack itself and errors supplied to this() propagate.

Optionally, if limit is supplied to parEach, at most limit callbacks will be active at a time.

seqMap(cb)

Like seqEach, but collect the values supplied to this and set the stack to these values.

parMap(cb)

parMap(limit, cb)

Like parEach, but collect the values supplied to this and set the stack to these values.

do(cb)

Create a new nested context. cb's first argument is the previous context.

flatten(fully=true)

Recursively flatten all the arrays in the stack. Set fully=false to flatten only one level.

extend([x,y...])

Like push, but takes an array. This is like python's [].extend().

set([x,y...])

Set the stack to a new array.

empty()

Set the stack to [].

push(x,y...), pop(), shift(), unshift(x), splice(...)

Executes an array operation on the stack.

Explicit Parameters

For environments like coffee-script or nested logic where threading this is bothersome, you can use:

  • seq_
  • par_
  • forEach_
  • seqEach_
  • parEach_
  • seqMap_
  • parMap_

which work exactly like their un-underscored counterparts except for the first parameter to the supplied callback is set to the context, this.

Context

Each callback gets executed with its this set to a function in order to yield results, error values, and control. The function also has these useful fields:

stack

The execution stack.

stack_

The previous stack value, mostly used internally for hackish purposes.

vars

A hash of key/values populated with par(key, ...), seq(key, ...) and this.into(key).

into(key)

Instead of sending values to the stack, sets a key and returns this. Use this.into(key) interchangeably with this for yielding keyed results. into overrides the optional key set by par(key, ...) and seq(key, ...).

args

this.args is like this.stack, but it contains all the arguments to this() past the error value, not just the first. this.args is an array with the same indices as this.stack but also stores keyed values for the last sequential operation. Each element in this.array is set to [].slice.call(arguments, 1) from inside this().

error

This is used for error propagation. You probably shouldn't mess with it.

Installation

With npm, just do: npm install seq

or clone this project on github:

git clone http://github.com/substack/node-seq.git

To run the tests with expresso, just do:

expresso

Dependencies

This module uses chainsaw When you npm install seq this dependency will automatically be installed.