React is a javascript module to make it easier to work with asynchronous code,
by reducing boilerplate code and improving error and exception handling while
allowing variable and task dependencies when defining flow. This project is
applying the concepts of Reactive programming or Dataflow to controlling
application flow.
This async flow control module is initially designed to work with Node.js but
is planned to be extended to browser and other environments.
React gets its name from similarities with how "chain reactions" work in the physical world. You start the reaction and then it cascades and continues until complete.
Also "Reactive Programming" or "Dataflow" describe defining flow which reacts to the data similar to how a spreadsheet updates cells. These are good examples of how React controls flow based on when data is available
The tasks can be mixed, meaning you can use async, sync, object method calls, class method calls, etc in the same flow.
Borrowing heavily from Tim and Elijah's ideas for conductor, this async flow control module provides a way to construct a flow from a collection of functions or methods (referred to as tasks in this module). It allows dependencies to be defined between the tasks so they can run in parallel as their dependencies are satisfied. React can us both variable dependencies and task dependencies.
As tasks complete, React watches the dependencies and kicks off additional tasks that have all their dependencies met and are ready to execute. This allows the flow to run at maximum speed without needing to arbitrarily block tasks into groups of parallel and serial flow.
To reduce the boilerplate code needed and improve error handling, React automatically provides callback functions for your asynchronous code. These React-provided callback functions perform these steps:
These live in the examples folder so they are ready to run.
Also see test/module-use.test.js for more examples as well
as the specific tests for the DSL you want to use.
### Example using default DSL
var react = require('react');
function loadUser(uid, cb){ }
function loadFile(filename, cb){ }
function markdown(filedata) { }
function writeOutput(html, user, cb){ }
function loadEmailTemplate(cb) { }
function customizeEmail(user, emailHtml, cb) { }
function deliverEmail(custEmailHtml, cb) { }
var loadAndSend = react('loadAndSend', 'uid, filename, cb -> err, user',
loadUser, 'uid, cb -> err, user',
loadFile, 'filename, cb -> err, filemd',
markdown, 'filemd -> html',
writeOutput, 'html, user, cb -> err, htmlBytesWritten',
loadEmailTemplate, 'cb -> err, emailmd',
markdown, 'emailmd -> emailHtml',
customizeEmail, 'user, emailHtml, cb -> err, custEHtml',
deliverEmail, 'custEHtml, cb -> err, custBytesWritten'
);
exports.loadAndSend = loadAndSend;
var foo = require('foo');
foo.loadAndSend(100, 'bar.md', function (err, user) {
}
### Example directly using AST
var react = require('react');
function load(res, cb) { setTimeout(cb, 100, null, res + '-loaded'); }
function prefix(prefstr, str, cb) { setTimeout(cb, 100, null, prefstr + str); }
function postfix(str, poststr, cb) { setTimeout(cb, 100, null, str + poststr); }
function upper(str) { return str.toUpperCase(); }
var fn = react();
var errors = fn.setAndValidateAST({
inParams: ['res', 'prefstr', 'poststr'],
tasks: [
{ f: load, a: ['res'], out: ['lres'] },
{ f: upper, a: ['lres'], out: ['ulres'], type: 'ret' },
{ f: prefix, a: ['prefstr', 'ulres'], out: ['plres'] },
{ f: postfix, a: ['plres', 'poststr'], out: ['plresp'] }
],
outTask: { a: ['plresp'] }
});
console.error('errors:', errors);
fn('foo', 'pre-', '-post', function cb(err, lres) {
console.error('err:', err);
console.error('lres:', lres);
});
### Example using Function String DSL interface
var react = require('react');
function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
function markdown(filedata) { return 'html'+filedata; }
function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
function customizeEmail(user, emailHtml, cb) { return 'cust-'+user+emailHtml; }
function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
function useHtml(err, html, user, bytesWritten) {
if(err) {
console.log('***Error: %s', err);
return;
}
console.log('final result: %s, user: %s, written:%s', html, user, bytesWritten);
}
var loadAndSave = react.fstrDefine('filename, uid, outDirname, cb', [
loadUser, 'uid -> err, user',
loadFile, 'filename -> err, filedata',
markdown, 'filedata -> returns html',
prepareDirectory, 'outDirname -> err, dircreated',
writeOutput, 'html, user -> err, bytesWritten', { after: prepareDirectory },
loadEmailTemplate, ' -> err, emailmd',
markdown, 'emailmd -> returns emailHtml',
customizeEmail, 'user, emailHtml -> returns custEmailHtml',
deliverEmail, 'custEmailHtml -> err, deliveredEmail', { after: writeOutput }
], 'err, html, user, bytesWritten');
loadAndSave('file.md', 100, '/tmp/foo', useHtml);
### Example using pseudocode DSL interface
var react = require('react');
function multiply(a, b, cb) { cb(null, a * b); }
function add(a, b) { return a + b; }
var locals = {
multiply: multiply,
add: add
};
var fn = react.pcodeDefine('a, b, cb', [
'm := multiply(a, b)',
's = add(m, a)',
'cb(err, m, s)'
], locals);
fn(2, 3, function (err, m, s) {
console.error('err:', err);
console.error('m:', m);
console.error('s:', s);
});
### Example using jquery-like chaining DSL interface
var react = require('react');
function multiply(a, b, cb) { cb(null, a * b); }
function add(a, b) { return a + b; }
var fn = react.chainDefine()
.in('a', 'b', 'cb')
.out('err', 'm', 's')
.async(multiply).in('a', 'b', 'cb').out('err', 'm')
.sync(add).in('m', 'a').out('s')
.end();
fn(2, 3, function (err, m, s) {
console.error('err:', err);
console.error('m:', m);
console.error('s:', s);
});
Status
- 2012-01-10 - Create default DSL for react()
- 2011-12-21 - Refactor from ground up with tests, changes to the interfaces
- 2011-10-26 - React is in active development and interface may change frequently in these early stages. Current code is functional but does not perform validation yet. Additional interfaces are planned to make it easy to define flows in a variety of ways. Documentation and examples forthcoming.
Test Results
ok ast.test.js .................... 10/10
ok cb-task.test.js ................ 31/31
ok chain.test.js .................. 56/56
ok core.test.js ................... 98/98
ok dsl.test.js .................... 58/58
ok event-manager.test.js .......... 13/13
ok exec-options.test.js ............. 3/3
ok finalcb-task.test.js ............. 5/5
ok fstr.test.js ................... 64/64
ok input-parser.test.js ........... 15/15
ok module-use.test.js ............. 64/64
ok pcode.test.js .................. 65/65
ok ret-task.test.js ............... 31/31
ok task.test.js ..................... 1/1
ok validate-cb-task.test.js ......... 6/6
ok validate-ret-task.test.js ........ 7/7
ok validate.test.js ............... 26/26
ok vcon.test.js ................... 42/42
total ........................... 613/613
ok
License