Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

A utility library to simplify asynchronous callback chains

branch: master
Readme.md

Cascade : a Node.js module for asynchronous callback chains

Cascade aims to simplify the callback chains promoted by Node's event-driven styles by declaring sequences of callbacks; instead of nesting multiple callbacks in various asynchronous functions, a call to cascade( item, func1, func2 ); will automatically pass item to func1, then pass the callback result of func1 to func2, and so on.

Compare the following two examples; the first is directly from Node.js (v 0.6.8) documentation, and the second is the same function written with Cascade:

/** Node.js Documentation, http://nodejs.org/docs/v0.6.8/api/fs.html#file_System **/
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));
  });
});

/** With Cascade.js **/
cascade( '/tmp/hello', '/tmp/world',
         cascade.chain( fs.rename ),  // invoke fs.rename( '/tmp/hello', '/tmp/world', next )
         cascade.raise( null, 2 ),    // throw an error if fs.rename calls next with an Error, otherwise
                                      //          shift 2 arguments
         fs.stat,                     // invoke fs.stat( '/tmp/world' )
         cascade.raise,               // throw an error if fs.stat calls next with an Error
         function (stats) {    // log the file stats
             console.log('stats: ' + JSON.stringify(stats));
         }
       );

Or an example from the Mongoose ODM:

/** Mongoose.js, http://mongoosejs.com/docs/embedded-documents.html **/
BlogPost.findById(myId, function (err, post) {
  if (!err) {
    post.comments[0].remove();
    post.save(function (err) {
      // do something
    });
  }
});

/** With Cascade.js **/
cascade( myId,
         BlogPost.findById,                // invoke BlogPost.findById( myId, next )
         cascade.raise,                    // throw an error if BlogPost.findById calls next with an Error
         function (post, next) {           // custom handler for a post
             post.comments[0].remove();
             post.save( next )             // Invert control - pass next to an asynchronous function as a
         },                                //        callback and resume right where the sequence left off
         function (err) {                  // this replaces the handler in the post.save() call
             // do something
         }
       );

As asynchronous callbacks become more deeply nested, the advantage of Cascade.js becomes more apparent. Several builtin functions in Cascade also take advantage of the event-driven model of Node.js; cascade.fork, cascade.queue, and cascade.join provide a simple way to manage asynchronous callback sequences. Other functions allow simple manipulations to transform the callout data from one function into the correct arguments for the next. Below is a complete function reference for Cascade.js.

Installation / Download

The latest version of Cascade.js will be available on this Github repository (https://github.com/scottrabin/cascade). Alternatively, Cascade can be installed via NPM:

npm install cascade

Note: Cascade.js is BETA software, and has not yet been tested in any browser

Documentation

Cascade is invoked via cascade( value, [value2, ...], callback, [callback2, ...] );. The values specified in the initializer will be passed as arguments to the first callback; subsequent callbacks will be invoked with the arguments called out by the prior callback function.

Terminology:

  • Callback : the function invoked by Cascade
  • Callin / Call in : the arguments passed to the callback function (shortened to args...)
  • Callout / Call out : the arguments passed to the callout function

Methods


chain    cascade.chain( function( args..., callback ) )    Accepts: args..., next

Wraps the specified function in a Cascade-friendly function. The wrapped function will be invoked with the callin arguments args....

Calls out: the callouts from the specified function prepended to args....

(Note: if the wrapped function does not accept an asynchronous callback as the last parameter, cascade.chain will fail)

cascade( 2, 3, 4,
         function( val1, val2, val3, next ){
             next( 1 );
         },
         callout
       );
       // callout receives arguments : 1

cascade( 2, 3, 4,
         cascade.chain( function( val1, val2, val3, next ){
             next( 1 );
         }),
         callout
       );
       // callout receives arguments : 1, 2, 3, 4

each    cascade.each( function( item, index, array ) )    Accepts: args..., next

Invokes the specified function on each callin argument. If the only argument is an array, then the function is instead invoked on each element in the array.

Calls out: the callin arguments, unless they were modified by function.

cascade( 'one', 'two', 'three',
         cascade.each( function(s){
             console.log( s );
         }),
         callout
       );
       // console: 
       //          one
       //          two
       //          three
       // callout receives arguments : 'one', 'two', 'three'

filter    cascade.filter( function( item, index, array ) )    Accepts: args..., next

Invokes the specified function on each argument, assembling a new argument list to call out. If the only argument is an array, the function is instead applied to that array. Only arguments returning true from function are inserted into the new argument array.

Calls out: the arguments that returned true from function, or a filtered array if the only argument was an array.

cascade( 1, 2, 3, 4, 5, 6,
         cascade.filter( function(i){
             return ( i % 2 === 0 );  // return 'true' for even numbers
         }),
         callout
       );
       // callout receives arguments : 2, 4, 6

fork    cascade.fork    Accepts: Array, next

Begins a new cascade for each element of Array. If the remaining cascade has asynchronous functions, all elements will pass through the cascade until the asynchronous functions are reached, at which point execution will defer until all synchronous callbacks have resolved. If the remaining cascade contains no asynchronous functions, fork is indistinguishable from queue.

Calls out: each element of Array individually

cascade( [ 'file1', 'file2', 'file3' ],
         cascade.fork,
         fs.stat,
         function( err, stat ){
             console.log( stat );
         }
       );
       // console:
       //          [[ stats from first fs.stat to finish ]]
       //          [[ stats from second fs.stat to finish ]]
       //          [[ stats from third fs.stat to finish ]]
       // (not necessarily in order file1, file2, file3)

join    cascade.join    Accepts: value, next

Reassembles a destructured array (via cascade.fork or cascade.queue) using values called out from the previous callback. All elements from a destructured array are required to complete before cascade.join calls out. The reassembled array is composed of the callin value at the original position in the array.

Calls out: the reassembled array composed of callout values from the prior callback function, only after all elements are received

(Note: Only one value can be passed to cascade.join; multiple arguments will cause the cascade to fail)

(Note: All elements are required to complete; failure to do so will halt the cascade at the cascade.join call)

cascade( [ 'file1', 'file2', 'file3' ],
         cascade.fork,
         fs.stat,
         cascade.rearrange( 1 ),       // move the second callout argument `stat` to the first position
         cascade.join,
         callout
       );
       // callout receives arguments : [[ stats for file1 ]], [[ stats for file2 ]], [[ stats for file3 ]]

map    cascade.map( function( item, index, array ) )    Accepts: args..., next

Invokes the specified function on each argument, assembling a new argument list to call out. The newly assembled argument list is composed of the return values from function for each element of the original argument list. If the only argument is an array, the function is instead applied to that array.

Calls out: the return values from function applied to each argument, or the mapped array if the only argument was an array.

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

queue    cascade.queue    Accepts: Array, next

Serializes the Array cascade by invoking the full callback sequence on each element before proceeding to the next element of Array. Whereas cascade.fork parallelizes calls by giving precedence to returning asynchronous functions on a "first come, first served" basis, cascade.queue enforces completion on each element in the queue before proceeding to the next element.

cascade( [ 400, 300, 200, 100 ],
         cascade.queue,
         function( ms, next ){
             setTimeout( function(){
                 console.log( ms );
                 next( ms );          // cascade.queue requires callouts to continue execution
             }, ms );
     }
       );
       // console:
       //          400
       //          300
       //          200
       //          100
       // Compare to cascade.fork : the first returning call would be 100, not 400, and would
       // have resulted in a reversed console output.

raise    cascade.raise, cascade.raise( function(err), [shiftArgs = 1] )    Accepts: args..., next

When using the first signature, cascade.raise throws an error if the first argument is an Error object; otherwise, calls out all callin arguments except for the first argument. When using the second signature, a custom error handler can be supplied, along with a custom value for the number of arguments to shift off before calling out. If the error handler function is null, cascade.raise will default to throwing an called in Error, or shift shiftArgs from the callin arguments before calling out.

Calls out: all callin arguments except for the first shiftArg arguments

cascade( 'error message',
         function( m, next ){
             next( Error(m) );
         },
         cascade.raise        // throws Error: "error message"
       );

cascade( 'error message',
         function( m, next ){
             setTimeout( function(){
                 next( Error(m) );
             }, 100 );
         },
         cascade.raise( errorHandler ) // async error functions require a tailored handler,
       );                              // since thrown errors are outside the current context

cascade( 1, 2, 3, 4,
         cascade.raise( null, 2 ),     // defaults to throwing the callin error, or shifts 2 arguments off
         callout
       );
       // callout receives arguments : 3, 4

rearrange    cascade.rearrange( i, j, k, ... )    Accepts: args..., next

Rearranges callin arguments into a different arrangement of callout arguments. Callin values (i, j, k, etc.) represent indices in the callin argument list to replace at that position.

Calls out: arguments as defined by the index parameters

cascade( 1, 2, 3, 4, 5,
         cascade.rearrange( 3, 1, 2 ),  // pluck the [3], [1], and [2] indices from callin arguments
         callout
       );
       // callout receives arguments : 4, 2, 3

slice    cascade.slice( start, [end] )    Accepts: args..., next

Slices the callin arguments using the specified start and end positions. If the only argument is an array, the slice is applied to that array instead.

Calls out: the sliced callin arguments, or the sliced array argument if the only argument was an array.

cascade( 1, 2, 3, 4, 5,
         cascade.slice( 2, 3 ),
         callout
       );
       // callout receives arguments : 3, 4

License

Cascade.js is licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php). Further details may be found in the MIT-LICENSE.txt file included in this repository.

Something went wrong with that request. Please try again.