Permalink
Browse files

Updated to match Node.js core callback function signature. Renamed to…

… 'Groupie.' Refined open groups and chains.
  • Loading branch information...
1 parent ccc7be5 commit c8250b572d7160546646f2d95c63e3972482c6c9 @alexkwolfe committed Sep 2, 2010
Showing with 339 additions and 177 deletions.
  1. +77 −59 README.md
  2. +0 −61 lib/gang-bang.js
  3. +128 −0 lib/groupie.js
  4. +8 −9 package.json
  5. +24 −24 test/unit/chain_test.js
  6. +24 −24 test/unit/group_test.js
  7. +35 −0 test/unit/open_chain_test.js
  8. +43 −0 test/unit/open_group_test.js
View
136 README.md
@@ -1,85 +1,103 @@
-# Gang Bang
+# Groupie
-A flow control library for node.js for executing multiple functions as a group or in a chain, calling back when all functions have finished.
+A simple flow control library for node.js for executing multiple functions as a group or in a chain,
+calling back when all functions have finished.
## Installation
- npm install gang-bang
+ npm install groupie
## Usage
+
+Use `group` or `chain` to execute your functions. Group executes all functions at once,
+and chain executes them one-by-one, in the declared order. Here's how it looks:
- var gang = require('gang-bang'),
- puts = require('sys').puts;
+ var groupie = require('groupie');
- // the array of functions to execute. each calls the done function
- // upon completion, passing back relevant information.
- var functions = [
- function(done) { done('red'); },
- function(done) { done('green'); },
- function(done) { done('blue'); }
- ];
+ // the array of functions to execute. each calls the done function upon completion
+ var fxns = [function(done) { done(); }, function(done) { done(); }];
// execute functions concurrently, and callback when all functions have been called
- gang.group(functions, function(colors) {
- puts("All colors were collected: " + colors);
+ groupie.group(fxns, function(err, results) {
+ if (err) throw new Error("An error occurred!");
+ require('sys').puts("all functions have been executed");
});
// execute each one after the other, and callback when all functions have been called
- gang.chain(functions, function(colors) {
- puts("All colors were collected: " + colors);
+ groupie.chain(fxns, function(err, results) {
+ // ...
});
+
+### Your functions
-## Function results
-
-To retrieve "return values" from each of your functions, pass the return value the `done` callback.
-You must invoke `done` even if you don't want to return a value — it provides the execution flow
-control.
+Each of your functions must accept a callback (called `done` below), and invoke the callback
+when the function execution is complete.
-The return values are passed in an array to the callback in the same order that the functions
-are declared.
+Here's a sample function, written to work with Groupie:
+
+ function(done) {
+ fs.rename('/tmp/foo.txt', '/tmp/bar.txt', function(err) {
+ done(err, 'file renamed successfully');
+ });
+ }
+
+If your function handles an error, then pass it to the callback in the first
+parameter (i.e. `done('oh noes!')`). If no error occurs, pass a `null` error and an optional
+result to the callback (i.e. `done(null, 'all done!')`). The second parameter is discarded
+if you provide an error.
-## Handling null and undefined results
+Note that the `done` callback uses the same signature as Node core library callbacks, so you can
+use the function callback as the Node callback. The above example could be rewritten as:
+
+ function(done) {
+ fs.rename('/tmp/foo.txt', '/tmp/bar.txt', done);
+ }
+
+
+### Your function results
+
+The results of your function calls is available in second parameter of the group or chain
+callback. The results is an array of the values submitted by your functions in the `done`
+callback, in the same order that the functions are declared.
If the `done` method is called with a `null` parameter or no parameter, then `null` or
`undefined` will be returned with other function values to the callback.
-## Error handling
-
-If an error occurs in a function, pass the error to the `done` method. When an error occurs
-execution stops and the error is returned to the callback. Other result values collected up to that
-point are available in the second callback parameter.
-
-It's up to you to check for errors in the values passed to the callback function and behave accordingly.
+
+## Leaving a group or chain "open"
- var gang = require('gang-bang'),
+Adding functions to a `group` or `chain` dynamically rather than declaring them all
+up front is easy:
+
+ var groupie = require('groupie'),
puts = require('sys').puts;
- // the array of functions to execute. each calls the done function
- // upon completion, passing back relevant information.
- var functions = [
- function(done) { done('red'); },
- function(done) { done(new Error('something went wrong')); },
- function(done) { done('blue'); }
- ];
-
- // execute functions concurrently.
- // stop execution and callback when an error occurs.
- gang.group(functions, function(err, colors) {
- if (err instanceof Error) {
- puts("An error occurred: " + err);
- puts("Here are the colors collected before the error: " + colors);
- } else {
- puts("No error occurred. Here are the colors: " + err);
- }
+ // if you don't specify any functions, the group remains
+ // open until you invoke the finalize function
+ var group = groupie.group(function(err, colors) {
+ // handle the results
});
+
+ // add some functions into the group
+ group.add(function(done) { done(null, 'yellow'); });
+ group.add(function(done) { done(null, 'blue'); });
+
+ // close the group -- the callback can now be fired
+ group.finalize();
+
+Your function will be invoked immediately when added to a group, or if you're adding to a chain,
+it will be pushed to the tail of the chain (to be executed last).
+
+
+## Handling errors
+
+To notify groupie that an error has occurred, just pass the error to the function callback as
+the first parameter. Your group or chain callback will be invoked with the error and the results
+collected up to that point.
+
+If you're using a chain, the next function in the chain will not be invoked. If you're using a group,
+function executions will continue, but the group callback will be invoked with the error and the results,
+while future results (and errors) will be discarded.
- // execute each one after the other, and callback when the error occurs
- // non-error results are provided in the second parameter.
- gang.chain(functions, function(err, colors) {
- if (err instanceof Error) {
- puts("An error occurred: " + err);
- puts("Here are the colors collected before the error: " + colors);
- } else {
- puts("No error occurred. Here are the colors: " + err);
- }
- });
+If an unhandled error occurs in a function, the error will be caught by Groupie and automatically
+provided to your group or chain callback.
View
@@ -1,61 +0,0 @@
-/**
- * Creates a group of passed functions, and executes callback when all have completed
- * or when an error has been detected.
- */
-var sys = require('sys');
-
-exports.group = function (fxns, cb) {
- if (fxns.length === 0) {
- cb();
- return;
- }
-
- var items_left_to_execute = fxns.length;
- var results = [];
- var callGroupFunction = function(i, fxn) {
- var done = function(result) {
- if (result instanceof Error)
- return cb(result, results);
-
- results[i] = result;
-
- items_left_to_execute--;
- if (!items_left_to_execute)
- cb(results);
- };
- fxn(done);
- };
-
- for ( var i = 0; i < fxns.length; i++) {
- callGroupFunction(i, fxns[i]);
- }
-}
-/**
- * Executes all functions in order, and a callback when all have completed
- * or when an error has been detected
- */
-exports.chain = function (fxns, callback) {
- if (fxns.length === 0)
- return;
-
- var pos = 0;
- var results = [];
- var callNext = function() {
- var done = function(result) {
- pos++;
- if (result instanceof Error)
- return callback(result, results);
-
- results.push(result);
-
- if (fxns.length > pos) {
- callNext();
- } else {
- callback(results);
- }
- };
- fxns[pos](done);
- };
-
- callNext();
-}
View
@@ -0,0 +1,128 @@
+
+var sys = require('sys');
+
+exports.bind = function(scope) {
+ var _function = this;
+
+ return function() {
+ return _function.apply(scope, arguments);
+ }
+}
+
+/**
+ * Executes a group of functions concurrently, invoking a callback when all have completed
+ * or when an error occurs. Errors occur when an executed function throws an unhandled
+ * Error or when an error is passed to the callback.
+ *
+ * Results are returned to the callback in the order that they are declared. The results
+ * of functions that complete after an error have occurred are discarded.
+ *
+ * group([function(done){ done(null, 1); }, function(done){ done(null, 2); }], function(err, results){});
+ * or
+ * var g = group(function(err, results) {});
+ * g.add(function(done) { done(null, 1); });
+ * g.add(function(done) { done(null, 2); });
+ * g.finalize();
+ */
+exports.group = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var cb = args.pop();
+ var fxns = args.length > 0 && Array.isArray(args[0]) ? args.shift() : [];
+ var open = !fxns.length;
+ var finalized = !open;
+
+ var items_left_to_execute = fxns.length;
+ var results = [];
+ var errOccurred = false;
+ var callGroupFunction = function(i, fxn) {
+ var done = function(err, result) {
+ if (errOccurred) return;
+ if (err) {
+ errOccurred = true;
+ return cb(err, results);
+ }
+ results[i] = result;
+ items_left_to_execute--;
+ if (finalized && !items_left_to_execute)
+ cb(null, results)
+ };
+
+ try {
+ fxn(done);
+ } catch (err) {
+ done(err, results);
+ }
+ };
+
+ for ( var i = 0; i < fxns.length; i++) {
+ callGroupFunction(i, fxns[i]);
+ }
+
+ if (open)
+ return {
+ add: function(fxn) {
+ items_left_to_execute++;
+ callGroupFunction(fxns.push(fxn) - 1, fxn);
+ },
+ finalize: function() {
+ finalized = true;
+ if (!errOccurred && !items_left_to_execute)
+ cb(null, results);
+ }
+ }
+}
+/**
+ * Executes all functions in order, and a callback when all have completed
+ * or when an error has been detected.
+ *
+ * The callback takes two arguments: an error (or null if no error occurred),
+ * and the results of the chain operation, in order.
+ */
+exports.chain = function () {
+ var args = Array.prototype.slice.call(arguments);
+ var cb = args.pop();
+ var fxns = args.length > 0 && Array.isArray(args[0]) ? args.shift() : [];
+ var open = !fxns.length;
+ var finalized = !open;
+
+ var pos = 0;
+ var results = [];
+
+ var hasMore = function() {
+ return fxns.length > pos;
+ };
+
+ var callNext = function() {
+ var done = function(err, result) {
+ if (err) return cb(err, results);
+
+ pos++;
+ results.push(result);
+
+ if (hasMore())
+ callNext();
+ else if (finalized)
+ cb(null, results);
+ };
+ try {
+ fxns[pos](done);
+ } catch (err) {
+ cb(err, results);
+ }
+ };
+
+ if (fxns.length > 0)
+ callNext();
+
+ if (open)
+ return {
+ add: function(fxn) {
+ fxns.push(fxn);
+ if (hasMore()) callNext();
+ },
+ finalize: function() {
+ finalized = true;
+ if (!hasMore()) cb(null, results);
+ }
+ }
+}
View
@@ -1,24 +1,23 @@
-{ "name" : "gang-bang"
-, "description" : "An execution flow-control utility."
-, "version" : "0.1.3"
-, "homepage" : "http://github.com/alexkwolfe/gang-bang"
+{ "name" : "groupie"
+, "description" : "A simple flow control library for node.js for executing multiple functions as a group or in a chain, calling back when all functions have finished."
+, "version" : "0.1.0"
+, "homepage" : "http://github.com/alexkwolfe/groupie"
, "author" : "Alex Wolfe <alexkwolfe@gmail.com>"
, "contributors" : []
, "repository" :
{ "type" : "git"
- , "url" : "http://github.com/alexkwolfe/gang-bang.git"
+ , "url" : "http://github.com/alexkwolfe/groupie.git"
}
, "bugs" :
{ "mail" : "alexkwolfe@gmail.com"
- , "web" : "http://github.com/alexkwolfe/gang-bang/issues"
+ , "web" : "http://github.com/alexkwolfe/groupie/issues"
}
-, "directories" : { "lib" : "./lib" }
-, "main" : "./lib/gang-bang"
+, "main" : "./lib/groupie"
, "engines" : { "node" : ">=0.1.101" }
, "dependencies" : { }
, "licenses" :
[ { "type" : "MIT"
- , "url" : "http://github.com/alexkwolfe/gang-bang/raw/master/LICENSE"
+ , "url" : "http://github.com/alexkwolfe/groupie/raw/master/LICENSE"
}
]
}
Oops, something went wrong.

0 comments on commit c8250b5

Please sign in to comment.