🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

aflow

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aflow - npm Package Compare versions

Comparing version
0.2.3
to
0.9.0
+25
-18
lib/qflow.js

@@ -63,3 +63,3 @@ /**

function _loop(cb) {
if (test()) func(function(err, ret) { cb(err, false); });
if (test()) func(function(err) { cb(err, false); });
else cb(null, true);

@@ -79,3 +79,3 @@ },

if (!Array.isArray(dataItems)) {
if (!dataItems || typeof dataItems.length !== 'number') {
return callback(new Error("expected a data array, but got " + typeof data));

@@ -108,3 +108,4 @@ }

var lastResult;
var lastResult, lastResult2;
var callReturned;
applyVisitor(

@@ -115,8 +116,13 @@ functionList,

// it will become the next func`s lastResult
// forwards the first 2 results to the next function in the chain
callReturned = false;
func(
function(err, ret) {
function(err, ret, ret2) {
callReturned = true;
lastResult = ret;
cb(err, ret);
lastResult2 = ret2;
cb(err, ret, ret2);
},
lastResult
lastResult,
lastResult2
);

@@ -126,6 +132,5 @@ },

// return the very last result computed, that applyVisitor returns
return callback ? callback(err, lastResult) : null;
return callback ? (callReturned ? callback(err, lastResult, lastResult2) : callback(err)) : null;
}
);
// node v0.11.13 runs this 30% slower than v0.10.25
}

@@ -135,5 +140,7 @@

* Combine all items in the data array using func.
* runningTotal is the initial combined value; a call to combinerFunc is given
* the running total and item and the return value is the new runningTotal.
* This is an async variant of Array.prototype.reduce().
*/
// TODO: testme
function reduce( data, combinerFunc, runningTotal, callback ) {
function reduce( data, runningTotal, combinerFunc, callback ) {
'use strict';

@@ -144,7 +151,7 @@

function visitor(item, cb) {
combinerFunc(item, function(err, ret) {
combinerFunc(runningTotal, item, function(err, ret) {
// the running total is the combined results of item and the
// previous subtotal, as computed by func().
runningTotal = ret;
return err ? cb(err) : cb(null, runningTotal);
if (!err || ret !== undefined) runningTotal = ret;
return cb(err, runningTotal);
});

@@ -164,3 +171,2 @@ },

*/
// TODO: testme
function map( data, mapperFunc, callback ) {

@@ -175,3 +181,3 @@ // TODO: might also want to map properties of an object, not just arrays

mapperFunc(item, function(err, mappedItem) {
mappedResults.push(mappedItem);
if (!err || mappedItem !== undefined) mappedResults.push(mappedItem);
cb(err);

@@ -188,4 +194,5 @@ });

* Return the list of items that were picked by the filter function.
* The filter function should return truthy for values to include,
* and falsy for values to omit from the results.
*/
// TODO: testme
function filter( data, filterFunc, callback ) {

@@ -196,4 +203,4 @@ var pickedItems = [];

function visitor(item, cb) {
filterFunc(item, function(err, ret) {
if (ret && !err) pickedItems.push(item);
filterFunc(item, function(err, yesno) {
if (yesno) pickedItems.push(item);
cb(err);

@@ -200,0 +207,0 @@ });

{
"name": "aflow",
"version": "0.2.3",
"version": "0.9.0",
"description": "async flow control for calls with callbacks",

@@ -24,5 +24,14 @@ "license": "Apache-2.0",

"quick",
"async",
"fast",
"recursion-safe",
"flow",
"control",
"async"
"repeatUntil",
"repeatWhile",
"applyVisitor",
"iterate",
"reduce",
"map",
"filter"
],

@@ -32,3 +41,4 @@ "dependencies": {

"devDependencies": {
"nodeunit": "0.9.0"
}
}
+142
-40

@@ -6,14 +6,20 @@ aflow

## Install
All functions run sequentially and stop immediately on error.
## Installation
npm install aflow
npm test aflow
or `git clone git://github.com/andrasq/node-aflow`
## Calls
## Functions
### repeatUntil( function, callback )
### repeatUntil( repeatedFunc(cb), done(err, stoppedWithValue) )
keep calling function until it returns a truthy value, then return that value
via callback.
Keep calling the repeated function until it returns a truthy value, then
return that value via the callback. Stops looping on error. This call is
recursion-safe, it periodically yields to the event loop with setImmediate().
The repeated call must call its callback, the loop will wait indefinitely and
will not time out.

@@ -23,53 +29,149 @@ var aflow = require('aflow');

aflow.repeatUntil(
function(cb) {
function repeated(cb) {
count += 1;
var stopNow = count >= 10;
cb(null, stopNow);
if (count < 10) cb(null, false);
else cb(null, count);
},
function(err) {
console.log("count =", count);
function whenDone(err, stoppingCount) {
console.log("stoppingCount = %d, count = %d", stoppingCount, count);
// => stoppingCount = 10, count = 10
}
)
// => count = 10
);
### repeatWhile( loopCondition, function, callback )
### repeatWhile( loopTest(), repeatedFunc(cb), done(err) )
keep calling function as long as loopCondition holds.
Keep calling the repeated function as long as the loopTest() function returns
truthy. loopTest() does not take arguments or a callback. Stops looping on
error. Any value returned by the repeated function is ignored.
var aflow = require('aflow');
var count = 0;
aflow.repeatWhile(
function loopTest() {
return count < 10;
},
function repeated(cb) {
count += 1;
cb();
},
function whenDone(err) {
console.log("final count =", count);
// => final count = 10
}
);
### applyVisitor( items, visitorFunc(item, cb), done(err) )
Invoke the visitor function on each data item in turn. Stops on error and
returns the error object. Does not return a value.
var aflow = require('aflow');
var count = 0;
aflow.repeatWhile(
function() {
return count < 10;
},
function(cb) {
count += 1;
var visitedItems = [];
aflow.applyVisitor(
[1,2,3,4],
function visitor(item, cb) {
visitedItems.push(2 * item);
cb();
},
function(err) {
console.log("count =", count);
function whenDone(err) {
// visitedItems = [2,4,6,8];
}
)
// => count = 10
);
### iterate( functionList, callback )
### iterate( functionList, done(err, ret1, ret2) )
call each function in turn. Each function is passed two arguments, a
callback and the returned value from the previous function. An error
will interrupt the flow. Callback will be called with the returned
value from the last function, or the error.
Call each function on the list. The functions are passed three arguments, a
callback and the first two returned values from the previous function called.
An error will interrupt the flow. The when done callback will be called with
the first two returned values from the last function. On error, the callback
will be called with the error and the first two results returned by the call
that produced the error (if the call returned results).
var aflow = require('aflow');
var args = [];
aflow.iterate([
function(cb, last) { console.log("first, last", last); cb(null, 1); },
function(cb, last) { console.log("second, last", last); cb(null, 2); },
function(cb, last) { console.log("third, last", last); cb(null, 3); }
function(cb, arg1, arg2) { args.push(arg1); args.push(arg2); cb(null, 1, 11); },
function(cb, arg1, arg2) { args.push(arg1); args.push(arg2); cb(null, 2, 22,); },
function(cb, arg1, arg2) { args.push(arg1); args.push(arg2); cb(null, 3, 33, 333); },
],
function(err, last) {
console.log("done, last", last);
function(err, arg1, arg2, arg3) {
args.push(arg1);
args.push(arg2);
args.push(arg3);
// args = [undefined, undefined, 1, 11, 2, 22, 3, 33, undefined]
}
);
// => first, last undefined
// => second, last 1
// => third, last 2
// => done, last 3
### reduce( items, subtotal, combineFunc(subtot, item, cb), done(err, total) )
Combine all data items with the subtotal using the given combine function.
The result returned from the last combine function becomes the subtotal passed
to the next next combine function. Stops processing on error and returns the
subtotal so far. If a combiner function returs a defined subtotal along with
an error object, that subtotal will be the final subtotal returned.
var aflow = require('aflow');
aflow.reduce(
['a', 'b', 'c', 'd'],
':',
function concatenate(subtotal, item, cb) {
cb(null, subtotal + item);
},
function whenDone(err, total) {
// total = ":abcd"
}
);
### map( items, transformFunc(item, cb), done(err, transformedItems) )
Apply the transformation to each data item, and return the list of transformed
items. The transfor function is provided the item and a callback, and should
call its callback with any error and the transformed item. Stops and returns
the partial results so far on error. If the transform returns an error object
along with a defined result, it will be included in the partial results.
var aflow = require('aflow');
aflow.map(
[1,2,3,4,5],
function double(item, cb) {
cb(null, 2 * item);
},
function whenDone(err, transformedItems) {
// transformedItems = [2,4,6,8,10]
}
);
### filter( items, selectFunc(item, cb), done(err, selectedItems) )
Evaluate each data item with the select function and return the list of items
that were selected. The select function is provided the item and a callback,
and should call its callback with any error and a truthy value to select that
item. If the select function returns an error object along with a truthy
selection flag, the item generating the error will be included in the partial
results.
var aflow = require('aflow');
aflow.filter(
[1,2,3,4,5],
function oddItems(item, cb) {
if (item % 2) cb(null, true);
else cb(null, false);
},
function whenDone(err, selectedItems) {
// selectedItems = [1,3,5]
}
);
## ChangeLog
0.9.0
- document all calls
- better unit test coverage
- allow applyVisitor() to iterate any object with a numeric length, eg `arguments` or a `Buffer`
## Todo
- benchmark the speed of each of the primitives

@@ -15,2 +15,7 @@ 'use strict';

'should parse package.json': function(t) {
require('../package.json');
t.done();
},
'repeatUntil': {

@@ -24,3 +29,3 @@ 'should trap errors and return them in callback': function(t) {

function(err, last) {
t.ok(err);
t.ok(err instanceof Error);
t.equal(err.message, "die");

@@ -62,2 +67,261 @@ t.done();

},
'repeatWhile': {
'should test loop condition at top': function(t) {
var ncalls = 0;
t.expect(1);
qflow.repeatWhile(
function() { return false; },
function(next) { ncalls += 1; next(); },
function(err) {
t.equal(ncalls, 0);
t.done();
}
);
},
'should loop n times': function(t) {
var ncalls = 0;
t.expect(1);
qflow.repeatWhile(
function() { return ncalls < 1234; },
function(next) { ncalls += 1; next(); },
function(err) {
t.equal(ncalls, 1234);
t.done();
}
);
},
'should stop loop on error': function(t) {
var ncalls = 0;
t.expect(3);
qflow.repeatWhile(
function() { return ncalls < 1234; },
function(next) { ncalls += 1; if (ncalls < 111) next(); else throw new Error("stop at 111"); },
function(err) {
t.equal(ncalls, 111);
t.ok(err instanceof Error);
t.equal(err.message, "stop at 111");
t.done();
}
);
},
},
'applyVisitor': {
'should call visitor with each item': function(t) {
var visited = [];
qflow.applyVisitor(
[1,2,3,4],
function(item, cb) {
visited.push(item);
cb();
},
function(err) {
t.deepEqual(visited, [1,2,3,4]);
t.done();
}
);
},
'should return error and stop looping on error': function(t) {
var visited = [];
t.expect(3);
qflow.applyVisitor(
[1,2,3,4],
function(item, cb) {
visited.push(item);
if (item == 3) throw new Error("stop at 3");
cb();
},
function(err) {
t.deepEqual(visited, [1,2,3]);
t.ok(err instanceof Error);
t.equal(err.message, "stop at 3");
t.done();
}
);
},
},
'iterate': {
'should call each function': function(t) {
var items = [];
t.expect(1);
qflow.iterate([
function(cb) { items.push(1); cb() },
function(cb) { items.push(2); cb() },
function(cb) { items.push(3); cb() },
],
function(err) {
t.deepEqual(items, [1,2,3]);
t.done();
}
);
},
'should pass previous result to each next function and return the last result': function(t) {
var args = [];
t.expect(3);
qflow.iterate([
function(cb, arg, arg2) { args.push(arg); args.push(arg2); cb(null, 1, 11); },
function(cb, arg, arg2) { args.push(arg); args.push(arg2); cb(null, 2, 22); },
function(cb, arg, arg2) { args.push(arg); args.push(arg2); cb(null, 3, 33); },
],
function(err, arg, arg2) {
t.deepEqual(args, [undefined, undefined, 1, 11, 2, 22]);
t.equal(arg, 3);
t.equal(arg2, 33);
t.done();
}
);
},
'should stop on error': function(t) {
var items = [];
t.expect(3);
qflow.iterate([
function(cb) { items.push(1); cb(); },
function(cb) { items.push(2); throw new Error("stop at 2"); cb() },
function(cb) { items.push(3); cb() },
],
function(err) {
t.deepEqual(items, [1,2]);
t.ok(err instanceof Error);
t.equal(err.message, "stop at 2");
t.done();
}
);
},
},
'reduce': {
'should combine items': function(t) {
var items = [1,2,3,4,5];
t.expect(1);
qflow.reduce(
items,
'',
function(total, item, cb) {
total += '.' + item;
cb(null, total);
},
function(err, final) {
t.equal(final, '.1.2.3.4.5');
t.done();
}
);
},
'returns errors along with partial results': function(t) {
var items = [1,2,3,4,5];
t.expect(3);
qflow.reduce(
items,
'',
function(total, item, cb) {
if (item === 3) throw new Error("err at 3");
total += '.' + item;
cb(null, total);
},
function(err, final) {
t.ok(err instanceof Error);
t.equal(err.message, "err at 3");
t.equal(final, ".1.2");
t.done();
}
);
},
},
'map': {
'should apply function to all items': function(t) {
var items = [1,2,3,4];
t.expect(1);
qflow.map(
items,
function(item, cb) { cb(null, item + item); },
function(err, mapped) {
t.deepEqual(mapped, [2,4,6,8]);
t.done();
}
);
},
'should return error and partial results': function(t) {
var items = [1,2,3,4];
t.expect(3);
qflow.map(
items,
function(item, cb) { if (item === 3) throw new Error("three"); else cb(null, item + item); },
function(err, mapped) {
t.deepEqual(mapped, [2,4]);
t.ok(err instanceof Error);
t.equal(err.message, "three");
t.done();
}
);
},
'should include a defined item that came with error': function(t) {
var items = [1,2,3,4];
t.expect(1);
qflow.map(
items,
function(item, cb) { cb((item === 3 ? new Error("three") : null), item); },
function(err, mapped) {
t.equal(mapped[2], 3);
t.done();
}
);
},
},
'filter': {
'should return array of items selected by filter function': function(t) {
var items = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
t.expect(1);
qflow.filter(
items,
function(item, cb) {
// odd multiples of 3
var yesInclude = (item % 2) && (item % 3 == 0)
cb(null, yesInclude);
},
function(err, found) {
t.deepEqual(found, [3, 9, 15]);
t.done();
}
);
},
'should return any errors': function(t) {
var items = [1,2,3,4];
t.expect(3);
qflow.filter(
items,
function(item, cb) { if (item == 3) throw new Error("err on 3"); cb(null, true); },
function(err, found) {
t.ok(err instanceof Error);
t.equal(err.message, "err on 3");
t.deepEqual(found, [1,2]);
t.done();
}
);
},
'should include a defined item that came with error': function(t) {
var items = [1,2,3,4];
t.expect(1);
qflow.filter(
items,
function(item, cb) { cb((item === 3 ? new Error("three") : null), item); },
function(err, picked) {
t.equal(picked[2], 3);
t.done();
}
);
},
},
};