flowless

0.0.2 • Public • Published

node-flowless - Less but better control-flow library

flowless is not a flawless :)

Examples

Here is the same example as asyncblock.

var fs = require('fs');
var flowless = require('flowless');
 
flowless.runSeq([
  flowless.par([
    [fs.readFile, 'path1', 'utf8'],
    [fs.readFile, 'path2', 'utf8']
  ]),
  function(results, cb) {
    fs.writeFile('path3', results.join(''), cb);
  },
  [fs.readFile, 'path3', 'utf8']
], function(err, result) {
  if (err) throw err;
  console.log(result);
  console.log('all done');
});

Installation

$ npm install flowless

Features

Easily nestable

flowless provides two kind of API for control-flow.

  • Runs asynchronous functions immediately:
    • runSeq(), runPar() and runMap().
  • Returns a function which runs asynchronous functions:
    • seq(), par() and map().

The former is similar to the function that other control-flow modules (e.g. Async) provide.

On the other hand, the latter returns a function which runs asynchronous functions, rather than running them immediately. Because the returned function is also an asynchronous style, they are easily nestable.

Example:

var flowless = require('flowless');
 
flowless.runSeq([
  function one() {...},
  function two() {...},
 
  flowless.par([
    flowless.seq([
      function three() {...},
      function four() {...},
    ]),
 
    flowless.seq([
      function five() {...},
      function six() {...},
    ])
  ]),
 
  flowless.map(
    function seven() {...}
  ),
 
  function eight() {...}
 
], function allDone() {...}
);

The structure of this program reflects a control-flow directly.

                                    +-> seven -+
             +-> three --> four -+  +-> seven -+
one --> two -+                   +--+-> seven -+-> eight --> allDone
             +-> five  --> six  -+  +-> seven -+
                                          ...

Easily composable

Example:

function foo() {
  return flowless.seq([
    function(...) {...},
    function(...) {...},
    ...
  ]);
}
function bar() {
  return flowless.par([
    function(...) {...},
    function(...) {...},
    ...
  ]);
}

You can put them together as follows.

flowless.runSeq([
  foo(),
  bar()
], function(err, result) {
  ...
});

Exception handling in the same way as an error

When an error is passed to a callback.

flowless.runSeq([
  function foo(cb) {
    cb(null);
  },
  function bar(cb) {
    cb(new Error('bar faild')); // Error is passed to cb
  },
  function baz(cb) {
    assert.fail('unreachable');
  }
], function allDone(err, result) {
  console.log(err);
});

When an exception is thrown.

flowless.runSeq([
  function foo(cb) {
    cb(null);
  },
  function bar(cb) {
    throw new Error('bar faild'); // Error is thrown
  },
  function baz(cb) {
    assert.fail('unreachable');
  }
], function allDone(err, result) {
  console.log(err);
});

In both of the cases, three() is not called and Error is passed to the allDone().

Flowless wraps the cause error. It has the information about the location in the control-flow that the error occurred.

Example:

var flowless = require('flowless');
flowless.runSeq([
  function foo(cb) {
    cb(null);
  },
  flowless.seq([
    function bar(cb) {
      cb(new Error('Oooops!!'));
    }
  ]),
], function(err, result) {
  console.log(err);
});

Result:

{ [Error: seq[0] at (/tmp/error.js:6) failed: [Error: Oooops!!]]
  cause: [Error: Oooops!!],
  history: 
   [ { operator: 'seq',
       index: 0,
       location: '(/tmp/error.js:6)',
       reason: 'failed' },
     { operator: 'runSeq',
       index: 1,
       location: '(/tmp/error.js:2)',
       reason: 'failed' } ] }

Logging for debug

If NODE\_DEBUG environment variable contains flowless, flowless outputs logs for debug. For example, the first example of this page, it is output log as follows:

$ NODE_DEBUG=flowless node ex.js
FLOWLESS: BEGIN runSeq at (/tmp/ex.js:4)
FLOWLESS: begin runSeq[0] at (/tmp/ex.js:4) with: [ [Function: next] ]
FLOWLESS: BEGIN par at (/tmp/ex.js:5)
FLOWLESS: begin par[0] at (/tmp/ex.js:5) with: [ [Function], 'path1', 'utf8' ]
FLOWLESS: begin par[1] at (/tmp/ex.js:5) with: [ [Function], 'path2', 'utf8' ]
FLOWLESS: end par[0] at (/tmp/ex.js:5) with: [ null, 'aaa\nbbb\nccc\n' ]
FLOWLESS: end par[1] at (/tmp/ex.js:5) with: [ null, 'xxx\nyyy\nzzz\n' ]
FLOWLESS: END par at (/tmp/ex.js:5) with: [ 'aaa\nbbb\nccc\n', 'xxx\nyyy\nzzz\n' ]
FLOWLESS: end runSeq[0] at (/tmp/ex.js:4) with : [ null, [ 'aaa\nbbb\nccc\n', 'xxx\nyyy\nzzz\n' ] ]
FLOWLESS: begin runSeq[1] at (/tmp/ex.js:4) with: [ [ 'aaa\nbbb\nccc\n', 'xxx\nyyy\nzzz\n' ], [Function: next] ]
FLOWLESS: end runSeq[1] at (/tmp/ex.js:4) with : [ null ]
FLOWLESS: begin runSeq[2] at (/tmp/ex.js:4) with: [ [Function: next] ]
FLOWLESS: end runSeq[2] at (/tmp/ex.js:4) with : [ null, 'aaa\nbbb\nccc\nxxx\nyyy\nzzz\n' ]
FLOWLESS: END runSeq at (/tmp/ex.js:4)
aaa
bbb
ccc
xxx
yyy
zzz

all done

Core API

var flowless = require('flowless');

flowless.seq(functions)

Returns an asynchronous style function which runs functions in sequential.

  • Arguments
    • functions: An array of functions. Each function can be an array as a Function Template that is explained below.
  • Returns
    • An asynchronous style function which runs functions in sequential.

Returned function is:

function([args...,] cb)
  • Arguments
    • args: Zero or more arguments that will be passed to first element of functions.
    • cb: A callback function.

If some function in the sequential throws an exception or passes an error to its callback, no more functions are invoked and cb is immediately called with the exception or error.

flowless.runSeq(functions [, cb])

Runs functions in sequential immediately.

  • Arguments
    • functions: An array of functions. Each function can be an array as a Function Template that is explained below.
    • cb: A callback function.

Example:

flowless.runSeq([
  function(cb) {
    ...
  },
  function(cb) {
    ...
  }
], function(err, result) {
  ...
});

is equivalent of:

var fn = flowless.seq([
  function(cb) {
    ...
  },
  function(cb) {
    ...
  }
]);
fn(function(err, result) {
  ...
});

flowless.par(functions)

Returns an asynchronous style function which runs functions in parallel.

If any of the functions throw an exception or pass an error to its callback, the main callback is immediately called with the value of the error.

  • Arguments
    • functions: An array of functions. Each function can be an array as a Function Template that is explained below.
  • Returns
    • An asynchronous style function which runs functions in parallel.

Returned function is:

function([args...,] cb)
  • Arguments
    • args: Zero or more arguments that will be passed to all element of functions.
    • cb: A callback function.

If any functions in the parallel throw an exception or pass an error to its callback, cb is immediately called with the exception or error.

flowless.runPar(functions [, cb])

Runs functions in parallel immediateley.

  • Arguments
    • functions: An array of functions. Each function can be an array as a Function Template that is explained below.
    • cb: A callback function.

Example:

flowless.runPar([
  function(cb) {
    ...
  },
  function(cb) {
    ...
  }
], function(err, result) {
  ...
});

is equivalent of:

var fn = flowless.par([
  function(cb) {
    ...
  },
  function(cb) {
    ...
  }
]);
fn(function(err, result) {
  ...
});

flowless.map([concurrency,] fn)

Returns an asynchronous style function which produces a new array of values by mapping each value in the given array through the returned function.

  • Arguments
    • concurrency: A number of how many functions should be run in parallel. Defaults to 10.
    • fn: An asynchronous function which takes an element of the array as a first argument. It can be an array as a Function Template that is explained below.
  • Returns
    • An asynchronous style function which produces a new array of values by mapping each value in the given array through fn.

fn should be one of:

function(element, cb)
function(element, index, cb)
function(element, index, array, cb)
  • Arguments
    • element: An element of array passed to returned function.
    • index: An index number of the element.
    • array: An array passed to returned function.
    • cb: A callback function.

Returned function is:

function(array, cb)
  • Arguments
    • array: An array of values.
    • cb: A callback function.

If any functions in the parallel throw an exception or pass an error to its callback, cb is immediately called with the exception or error.

flowless.runMap([concurrency,] array, fn [, cb])

Produces a new array of values by mapping each value in the given array through the returned function.

  • Arguments
    • concurrency: A number of how many functions should be run in parallel. Defaults to 10.
    • array: an array of values.
    • fn: An asynchronous function. It can be an array as a Function Template that is explained below.
    • cb: A callback function.

fn should be one of:

function(element, cb)
function(element, index, cb)
function(element, index, array, cb)
  • Arguments
    • element: an element of array.
    • index: An index number of the element.
    • array: array passed to runMap().
    • cb: A callback function.

Example:

flowless.runMap(array, function(value, cb) {
  ...
}, function(err, result) {
  ...
});

is equivalent of:

var fn = flowless.map(function(value, cb) {
    ...
});
fn(array, function(err, result) {
  ...
});

Function Template

A Function template is an array representing a function and its argument. The first or second element must be a function. If the first element is not a function, it will be used as a context (this) object. The rest of elements will be used as the arguments of the function. The array can include a special value, flowless.first, flowless.second and flowless.third. When the function is called, they will be replaced with the actual argument of a corresponding position.

Example:

flowless.seq([
  ...
  ['fs.readFile', flowless.first, 'utf8'],
  ...
]);

is equivalent of:

flowless.seq([
  ...
  function(file, cb) {
    fs.readFile(file, 'utf8', cb);
  },
  ...
]);

Extra API

var extras = require('flowless').extras;

extras.array

Provides asynchronous style functions to wrap a method of Array. It takes the first argument as this.

Example:

flowless.seq([
  ...
  extras.array.join(':'),
  ...
]);

is equivalent of:

flowless.seq([
  ...
  function(self, cb) {
    cb(null, self.join(':'));
  },
  ...
]);

extras.string

Provides asynchronous style functions to wrap a method of String. It takes the first argument as this.

Example:

flowless.seq([
  ...
  extras.string.split(':'),
  ...
]);

is equivalent of:

flowless.seq([
  ...
  function(self, cb) {
    cb(null, self.split(':'));
  },
  ...
]);

extras.generate(args...)

Returns a new asynchronous style function which passes a given arguments to a callback.

  • Arguments
    • args: Values to be given in a callback.
  • Returns
    • An asynchronous style function which passes args to a callback.

Example:

flowless.seq([
  extras.generate(1, 2, 3),
  ...
]);

is equivalent of:

flowless.seq([
  function(cb) {
    cb(null, 1, 2, 3);
  },
  ...
]);

extras.bindFirst(target, name)

extras.bindSecond(target, name)

extras.bindThird(target, name)

Returns a new asynchronous style function which binds an argument of the specific position (first, second or third) to a property of a target object.

  • Arguments
    • target: An object that a value is bound.
    • name: A string of property name that a value is bound.
  • Returns
    • An asynchronous style function which binds an argument of the specific position to a property of target object.

Example:

var context = {};
flowless.seq([
  ...
  extras.bindFirst(context, 'name'),
  ...
]);

is equivalent of:

var context = {};
flowless.seq([
  ...
  function(name, cb) {
    context['name'] = name;
    cb(null, name);
  },
  ...
]);

extras.flattenFirst()

extras.flattenSecond()

extras.flattenThird()

Returns a new asynchronous style function which flatten an argument of the specific position (first, second or third).

  • Returns
    • An asynchronous style function which flatten an argument of the specific position. If an argument is not an array, it is passed to the callback.

Example:

var context = {};
flowless.seq([
  extras.generate(['foo', 'bar', 'baz']),
  extras.flattenFirst(),
  function(foo, bar, baz, cb) { // foo: 'foo', bar: 'bar', baz: 'baz'
    ...
  }
]);

extras.makeAsync(fn)

Returns a new asynchronous style function which invokes a synchronous function and passes the return value to a callback.

  • Arguments
    • fn: A synchronous function.
  • Returns
    • An asynchronous style function which invokes a synchronous function.

Example:

flowless.seq([
  ...
  extras.makeAsync(encodeURI),
  ...
]);

is equivalent of:

flowless.seq([
  ...
  function(uri, cb) {
    cb(encodeURI(uri));
  }),
  ...
]);

Acknowledgment

flowless is inspired by Slide.

License

flowless is licensed under the MIT license.

Readme

Keywords

none

Package Sidebar

Install

npm i flowless

Weekly Downloads

1

Version

0.0.2

License

none

Last publish

Collaborators

  • koichik