Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added tests; added cascade.collect; added cascade.slice for strings

  • Loading branch information...
commit 71ac3900d83708ca6169b7ce05864554dc7d034f 0 parents
@scottrabin authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules/*
27 index.js
@@ -0,0 +1,27 @@
+/**
+ * Cascade.js
+ *
+ * Simplifies the use of asynchronous series of function calls
+ */
+
+var requirejs = require('requirejs');
+
+requirejs.config({
+ baseUrl : __dirname + '/src',
+ nodeRequire : require
+});
+
+requirejs(
+ ['cascade_core',
+ 'cascade_fork', 'cascade_slice', 'cascade_queue', 'cascade_collect'],
+ function( cascade,
+ fork, slice, queue, collect ){
+
+ cascade.fork = fork;
+ cascade.slice = slice;
+ cascade.queue = queue;
+ cascade.collect = collect;
+
+ module.exports = cascade;
+ }
+);
22 package.json
@@ -0,0 +1,22 @@
+{
+ "author": "Scott Rabin <scottrabin@gmail.com>",
+ "name": "cascade",
+ "description": "Provides a set of functions to simplify calling sequental asynchronous methods",
+ "version": "0.0.1",
+ "repository": {
+ "url": ""
+ },
+ "main": "index.js",
+ "engines": {
+ "node": "~0.6.7"
+ },
+ "dependencies": {
+ "requirejs": "1.0.4"
+ },
+ "devDependencies": {
+ "vows": "0.5.x"
+ },
+ "scripts" : {
+ "test": "vows test/*.vows.js --spec"
+ }
+}
55 src/cascade_collect.js
@@ -0,0 +1,55 @@
+define( function(){
+ /**
+ * Collects a destructured list back into an array when all asynchronous calls are done
+ */
+
+ var collections = [];
+
+ return function( item, callback, data, callbackPosition, callbackStack ){
+ // ensure this item came from a list
+ if( !data.hasOwnProperty('list_index') || !data.hasOwnProperty('list_length') ){
+ callback( item );
+ }
+
+ // find the collection this item is associated with
+ var collection;
+ for( var i = 0; i < collections.length ; i++ ){
+ if( collections[i].stack === callbackStack ){
+ collection = collections[ i ];
+ break;
+ }
+ }
+
+ // if this is the first item from the collection
+ if( !collection ){
+ collection = {
+ // this is how the collection can be identified
+ stack : callbackStack
+ // instantiate a new sparsely-populated array for collecting items
+ , items : new Array( data.list_length )
+ // track how many more items will come in
+ , waiting_on : data.list_length
+ };
+
+ collections.push( collection );
+ }
+
+ // now have a collection that this item belongs to
+ collection.items[ data.list_index ] = item;
+ collection.waiting_on--;
+
+ // if we've collected all items, continue
+ if( collection.waiting_on === 0 ){
+ // remove this collection
+ for( i = 0 ; i < collections.length ; i++ ){
+ if( collections[i] === collection ){
+ collections.splice(i, 1);
+ break;
+ }
+ }
+
+ // call the callback with the collection
+ callback( collection.items );
+ }
+ };
+});
26 src/cascade_core.js
@@ -0,0 +1,26 @@
+define( ['util_extend'], function( extend ){
+
+ function cascade( item ){
+ // define the callback parameters and function
+ var callbackStack = Array.prototype.slice.call( arguments, 1 ),
+ curCallback = 0,
+ data = {},
+ callback = function( async_item, extraData ){
+ if( curCallback < callbackStack.length ){
+
+ callbackStack[ curCallback++ ]( async_item,
+ callback,
+ extend( data, extraData ),
+ curCallback,
+ callbackStack );
+ }
+ };
+
+ // start the callback chain
+ callback( item );
+
+ };
+
+ // return the cascade constructor
+ return cascade;
+});
29 src/cascade_fork.js
@@ -0,0 +1,29 @@
+define( ['util_extend'], function( extend ){
+
+ /**
+ * Forks an incoming list into separate series of callbacks for each item
+ */
+ return function( item, callback, parentData, curCallback, callbackStack ){
+
+ for( var i = 0, len = item.length ; i < len ; i++ ){
+ (function( element, index ){
+
+ // create a new callback function
+ var forkCallbackCount = curCallback,
+ data = extend( {}, parentData, { list_index : index, list_length : item.length } ),
+ forkCallback = function( forked_item, extraData ){
+ if( forkCallbackCount < callbackStack.length ){
+ callbackStack[ forkCallbackCount++ ]( forked_item,
+ forkCallback,
+ extend( data, extraData ),
+ forkCallbackCount,
+ callbackStack );
+ }
+ };
+
+ // start the new callback series
+ forkCallback( element );
+ })( item[i], i );
+ }
+ };
+});
51 src/cascade_queue.js
@@ -0,0 +1,51 @@
+define( function(){
+
+ /**
+ * Takes an array `item` and executes the remainder of the callback stack on each element
+ * of the array before advancing to the next element in the array
+ */
+ return function( item, callback, data, currentPosition, callbackStack ){
+
+ // current position in the array
+ var queuePosition = 0,
+ // current position in the callback stack
+ queueCallbackPosition = currentPosition,
+ // queue item specific data
+ queueData = _.extend( {},
+ data,
+ { list_index : queuePosition, list_length : item.length } ),
+ // the queue-specific callback function
+ queueCallback = function( nextItem, extraData ){
+ // typical callback execution
+ if( queueCallbackPosition < callbackStack.length ){
+
+ callbackStack[ queueCallbackPosition ]( nextItem,
+ queueCallback,
+ _.extend( queueData, extraData ),
+ queueCallbackPosition++,
+ callbackStack );
+ // when the end of the callback stack is reached
+ } else if ( queueCallbackPosition >= callbackStack.length ){
+ // move forward in the array `item`
+ queuePosition++;
+ // if this position exists in the item
+ if( queuePosition < item.length ){
+ // reset the counters and restart callback on next element
+ queueCallbackPosition = currentPosition;
+ queueData = _.extend( {},
+ data,
+ {
+ list_index : queuePosition,
+ list_length : item.length
+ });
+ queueCallback( item[ queuePosition ] );
+ }
+ }
+ };
+
+ // start the queue
+ queueCallback( item[ queuePosition ], queueData );
+
+ };
+
+});
27 src/cascade_slice.js
@@ -0,0 +1,27 @@
+define( ['util_is'], function( is ){
+
+ /**
+ * Takes an incoming array `item` and slices it according to the parameters, then passes the
+ * result to the next callback
+ */
+ return function(){
+ // clone & store args
+ var args = Array.prototype.slice.call( arguments );
+
+ // return the cascade function
+ return function( item, callback ){
+
+ if( is.array( item ) ){
+ // if it's an array, apply array slice
+ callback( Array.prototype.slice.apply( item, args ) );
+ } else if( is.string( item ) ){
+ // if it's a string, apply string slice
+ callback( String.prototype.slice.apply( item, args ) );
+ } else {
+ // not a slice-able item
+ callback( item );
+ }
+ };
+ };
+
+});
38 src/util_extend.js
@@ -0,0 +1,38 @@
+define( function(){
+
+ var toString = Object.prototype.toString,
+ hasOwnProperty = Object.prototype.hasOwnProperty,
+ isArray = function( a ){ return toString.call( a ) === '[object Array]'; },
+ isObject = function( o ){ return toString.call( o ) === '[object Object]'; };
+
+ return function extend( target ){
+ // local vars
+ var copy;
+ // loop through the rest of the arguments
+ for( var i = 1, srcLen = arguments.length ; i < srcLen ; i++ ){
+ // copy over all the properties from that object onto the target
+ for( var prop in arguments[ i ] ){
+ // ignore prototype properties
+ if( ! hasOwnProperty.call( arguments[ i ], prop ) ){ continue; }
+
+ copy = arguments[ i ][ prop ];
+
+ // ignore circular references
+ if( copy === target ){ continue; }
+ // ignore undefined values
+ else if( copy === undefined ){ continue; }
+
+ // deep copy arrays and objects
+ if( isArray( copy ) ){
+ target[ prop ] = extend( isArray( target[ prop ] ) ? target[ prop ] : [], copy );
+ } else if( isObject( copy ) ){
+ target[ prop ] = extend( isObject( target[ prop ] ) ? target[ prop ] : {}, copy);
+ } else {
+ target[ prop ] = copy;
+ }
+ }
+ }
+
+ return target;
+ };
+});
9 src/util_is.js
@@ -0,0 +1,9 @@
+define( function(){
+ var toString = Object.prototype.toString;
+
+ return {
+ array : function( o ){ return toString.call( o ) === '[object Array]'; },
+ string: function( o ){ return toString.call( o ) === '[object String]'; },
+ object: function( o ){ return toString.call( o ) === '[object Object]'; }
+ };
+});
91 test/core.vows.js
@@ -0,0 +1,91 @@
+var vows = require('vows'),
+ assert = require('assert');
+
+var cascade = require('../index'),
+ test = require('./test');
+
+vows.describe( 'Cascade' ).addBatch({
+ "Callbacks:" : {
+ "(one callback)" : test.context( 1, 2,
+ test.increment(1) ),
+ "(many callbacks)" : test.context( 1, 5,
+ test.increment(1),
+ test.increment(1),
+ test.increment(1),
+ test.increment(1) ),
+ "(with async call)" : test.context( 1, 5,
+ test.increment(1),
+ test.increment(1),
+ test.delay(100),
+ test.increment(1),
+ test.increment(1) )
+ },
+ "Data:" : {
+ "(one data, one callback)" : test.context( null, { data : true },
+ test.addData( { data : true } ),
+ test.returnData
+ ),
+ "(one data, many callbacks)" : test.context( null, { data : true },
+ test.addData( { data : true } ),
+ test.delay( 100 ),
+ test.returnData
+ ),
+ "(many data, many callbacks)" : test.context( null, { one : true, two : true },
+ test.addData( { one : true } ),
+ test.delay( 100 ),
+ test.addData( { two : true } ),
+ test.delay( 100 ),
+ test.returnData
+ )
+ },
+
+ "Slice:" : {
+ "(not an array)" : test.context( 1, 1,
+ cascade.slice(0) ),
+ "(array, length 1, slice(0))" : test.context( [1], [1],
+ cascade.slice(0)
+ ),
+ "(array, length 1, slice(1))" : test.context( [1], [],
+ cascade.slice(1)
+ ),
+ "(array, length 5, slice(0))" : test.context( [1,2,3,4,5], [1,2,3,4,5],
+ cascade.slice(0)
+ ),
+ "(array, length 5, slice(3))" : test.context( [1,2,3,4,5], [4,5],
+ cascade.slice(3)
+ )
+ },
+
+ "Collect:" : {
+ "(not from a destructured array, primitive)" : test.context( 1, 1,
+ cascade.collect
+ ),
+ "(not from a destructured array, array)" : test.context( [ 1 ], [ 1 ],
+ cascade.collect
+ ),
+ "(single item)" : test.context( [ 1 ], [ 1 ],
+ cascade.fork,
+ cascade.collect
+ ),
+ "(many items)" : test.context( [1, 2], [1, 2],
+ cascade.fork,
+ cascade.collect
+ )
+ },
+
+ "Fork:" : {
+ "(single item)" : test.context( [ 1 ], 1,
+ cascade.fork,
+ test.log
+ ),
+ "(many items, no post-process)" : test.context( [1, 2], [1, 2],
+ cascade.fork,
+ cascade.collect
+ ),
+ "(many items, post-process)" : test.context( [1, 2], [2, 3],
+ cascade.fork,
+ test.increment(1),
+ cascade.collect
+ )
+ }
+}).export(module);
77 test/test.js
@@ -0,0 +1,77 @@
+var assert = require('assert'),
+ cascade = require('../index');
+
+var test = module.exports = {
+ // generates a macro context
+ "context" : function( initialVal, result ){
+ var funcs = Array.prototype.slice.call( arguments, 2 );
+ return {
+ topic : function(){
+
+ cascade.apply( {},
+ [initialVal]
+ .concat( funcs )
+ .concat( test.done( this.callback ) )
+ );
+ },
+ "ok" : function( topic ){
+ assert.deepEqual( topic, result );
+ }
+ };
+ },
+
+ "increment" : function( by ){
+ return function( item, callback ){
+ callback( item + by );
+ };
+ },
+
+ "addData" : function( data ){
+ return function( item, callback ){
+ callback( item, data );
+ }
+ },
+
+ "returnData" : function( item, callback, data ){
+ callback( data );
+ },
+
+ "delay" : function( ms ){
+ return function( item, callback ){
+ setTimeout( function(){
+ callback( item );
+ }, ms );
+ }
+ },
+
+ "log" : function( item, callback ){
+ console.log( "Arguments:", item );
+
+ callback( item );
+ },
+
+ "done" : function( cb ){
+ return function( item, callback ){
+ cb( null, item );
+ }
+ },
+
+ /**
+ * Asynchronous-compatible assertion
+ */
+ "assertEqual" : function( value ){
+ return function( item, callback ){
+ assert.equal( item, value );
+
+ callback( item );
+ }
+ },
+
+ "assertDeepEqual" : function( obj ){
+ return function( item, callback ){
+ assert.deepEqual( item, obj );
+
+ callback( item );
+ }
+ }
+};
Please sign in to comment.
Something went wrong with that request. Please try again.