+220
| /** | ||
| * Flow control library to simplify sequencing calls with callbacks. | ||
| * | ||
| * Similar to async, but faster and more robust. Notable differences: | ||
| * - no more "call stack exceeded" from callbacks | ||
| * - callbacks must be called | ||
| * - callbacks can use process.nextTick without recursion | ||
| * - loop overhead is less | ||
| * - waterfall and series are combined | ||
| * | ||
| * Copyright (C) 2014 Andras Radics | ||
| * Licensed under the Apache License, Version 2.0 | ||
| * | ||
| * 2014-09-28 - AR. | ||
| */ | ||
| module.exports.repeatUntil = repeatUntil; | ||
| module.exports.applyVisitor = applyVisitor; | ||
| module.exports.repeatWhile = repeatWhile; | ||
| module.exports.map = map; | ||
| module.exports.filter = filter; | ||
| module.exports.reduce = reduce; | ||
| module.exports.iterate = module.exports.sequence = iterate; | ||
| // aliases | ||
| module.exports.qflow = iterate; | ||
| module.exports.flow = iterate; | ||
| module.exports.select = filter; | ||
| module.exports.series = iterate; | ||
| module.exports.whilst = repeatWhile; | ||
| var qflowSetImmediate = global.setImmediate ? function(f) { setImmediate(f) } : process.nextTick | ||
| /** | ||
| * Repeatedly call func until it signals stop (returns truthy) or err. | ||
| * Func is passed just a standard callback taking err and ret. | ||
| * Returns to the callback the error or the truthy value from func. | ||
| */ | ||
| function repeatUntil( func, callback ) { | ||
| 'use strict'; | ||
| callback = callback || function() {}; | ||
| var alreadyReturned; | ||
| var callDepth = 0; | ||
| function _invokeCallback( err, stop ) { | ||
| if (err || stop) { | ||
| if (alreadyReturned) throw new Error("callback already called"); | ||
| alreadyReturned = true; | ||
| return callback(err, stop); | ||
| } | ||
| else if (++callDepth < 40) { | ||
| _loop(); | ||
| } | ||
| else { | ||
| callDepth = 0; | ||
| qflowSetImmediate(_loop); | ||
| } | ||
| } | ||
| function _loop( ) { | ||
| try { | ||
| func( _invokeCallback ); | ||
| } | ||
| catch (err) { | ||
| // do not inject errors from within the callback back into the callback. | ||
| // If func returns without yielding, the callback will still be in | ||
| // our try/catch block. Re-throw that error like setImmediate would do. | ||
| // But if func has not returned yet, the error is from func, so return it. | ||
| if (alreadyReturned) throw err; else _invokeCallback(err); | ||
| } | ||
| } | ||
| _loop(); | ||
| } | ||
| /** | ||
| * Repeatedly call func as long as test returns truthy. | ||
| */ | ||
| function repeatWhile( test, func, callback ) { | ||
| repeatUntil( | ||
| function _loop(cb) { | ||
| if (test()) func(function(err) { cb(err, false); }); | ||
| else cb(null, true); | ||
| }, | ||
| callback | ||
| ); | ||
| } | ||
| /** | ||
| * visitor pattern: present all items to the visitor function. | ||
| * Returns just error status, no data; to capture the results, | ||
| * wrapper the visitor function. | ||
| */ | ||
| function applyVisitor( dataItems, visitorFunction, callback ) { | ||
| 'use strict'; | ||
| if (!dataItems || typeof dataItems.length !== 'number') { | ||
| return callback(new Error("expected a data array, but got " + typeof data)); | ||
| } | ||
| var next = 0; | ||
| repeatUntil( | ||
| function applyFunc(cb) { | ||
| if (next >= dataItems.length) return cb(null, true); | ||
| visitorFunction(dataItems[next++], function(err) { | ||
| cb(err, err); | ||
| }); | ||
| }, | ||
| function(err, stop) { | ||
| callback(err); | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Run the list of functions, calling each one in turn. | ||
| * Each function is passed a callback and the data item | ||
| * returned by the previous function. Errors interrupt the flow. | ||
| * The final result is the last result generated by the sequence | ||
| * (and not all the individual results). | ||
| */ | ||
| function iterate( functionList, callback ) { | ||
| 'use strict'; | ||
| var lastResult, lastResult2; | ||
| var callReturned; | ||
| applyVisitor( | ||
| functionList, | ||
| function visitor(func, cb) { | ||
| // send this computed result right back to the visitor loop, | ||
| // 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, ret2) { | ||
| callReturned = true; | ||
| lastResult = ret; | ||
| lastResult2 = ret2; | ||
| cb(err, ret, ret2); | ||
| }, | ||
| lastResult, | ||
| lastResult2 | ||
| ); | ||
| }, | ||
| function(err, ret) { | ||
| // return the very last result computed, that applyVisitor returns | ||
| return callback ? (callReturned ? callback(err, lastResult, lastResult2) : callback(err)) : null; | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * 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(). | ||
| */ | ||
| function reduce( data, runningTotal, combinerFunc, callback ) { | ||
| 'use strict'; | ||
| applyVisitor( | ||
| data, | ||
| function visitor(item, cb) { | ||
| combinerFunc(runningTotal, item, function(err, ret) { | ||
| // the running total is the combined results of item and the | ||
| // previous subtotal, as computed by func(). | ||
| if (!err || ret !== undefined) runningTotal = ret; | ||
| return cb(err, runningTotal); | ||
| }); | ||
| }, | ||
| function(err, ret) { | ||
| callback(err, runningTotal); | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Apply the function func to all the items in data, and return the | ||
| * results produced. Func must take a single argument and a callback. | ||
| * In case of error returns the partial results so far; | ||
| * the very last returned result is the one that errored out. | ||
| */ | ||
| function map( data, mapperFunc, callback ) { | ||
| // TODO: might also want to map properties of an object, not just arrays | ||
| // Approach: convert object to list, map list, de-convert results. | ||
| var mappedResults = []; | ||
| applyVisitor( | ||
| data, | ||
| function visitor(item, cb) { | ||
| mapperFunc(item, function(err, mappedItem) { | ||
| if (!err || mappedItem !== undefined) mappedResults.push(mappedItem); | ||
| cb(err); | ||
| }); | ||
| }, | ||
| function(err, ret) { | ||
| return callback(err, mappedResults);; | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * 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. | ||
| */ | ||
| function filter( data, filterFunc, callback ) { | ||
| var pickedItems = []; | ||
| applyVisitor( | ||
| data, | ||
| function visitor(item, cb) { | ||
| filterFunc(item, function(err, yesno) { | ||
| if (yesno) pickedItems.push(item); | ||
| cb(err); | ||
| }); | ||
| }, | ||
| function(err) { | ||
| callback(err, pickedItems); | ||
| } | ||
| ); | ||
| } |
+361
| 'use strict'; | ||
| var qflow = require('./index'); | ||
| function uniqid() { | ||
| return Math.floor(Math.random() * 0x1000000).toString(16); | ||
| } | ||
| module.exports = { | ||
| 'should include file': function(t) { | ||
| t.ok(qflow.repeatUntil); | ||
| t.done(); | ||
| }, | ||
| 'should parse package.json': function(t) { | ||
| require('./package.json'); | ||
| t.done(); | ||
| }, | ||
| 'repeatUntil': { | ||
| 'should trap errors and return them in callback': function(t) { | ||
| t.expect(2); | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| throw new Error("die"); | ||
| }, | ||
| function(err, last) { | ||
| t.ok(err instanceof Error); | ||
| t.equal(err.message, "die"); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| 'should stop looping when visitor returns truthy': function(t) { | ||
| var nloops = 0; | ||
| var id = uniqid(); | ||
| t.expect(2); | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(false, ++nloops >= 10 ? id : false); | ||
| }, | ||
| function(err, last) { | ||
| t.equal(last, id, "expected to see value that stopped loop"); | ||
| t.equal(nloops, 10, "expected 10 loopp iterations"); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| 'should not overflow stack': function(t) { | ||
| t.expect(1); | ||
| var nloops = 0; | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(false, ++nloops > 20000); | ||
| }, | ||
| function(err, last) { | ||
| t.ok(nloops == 20001); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| 'should re-throw subsequent func errors': function(t) { | ||
| t.expect(2); | ||
| try { | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(new Error("first error")); | ||
| throw new Error("second error"); | ||
| }, | ||
| function(err) { | ||
| t.equal(err.message, "first error"); | ||
| } | ||
| ); | ||
| } | ||
| catch (err) { | ||
| t.equal(err.message, "second error"); | ||
| t.done(); | ||
| } | ||
| }, | ||
| 'should re-throw callback errors': function(t) { | ||
| t.expect(1); | ||
| try { | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(null, true); | ||
| }, | ||
| function(err) { | ||
| throw new Error("callback error"); | ||
| } | ||
| ); | ||
| } | ||
| catch (err) { | ||
| t.equal(err.message, "callback error"); | ||
| 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(); | ||
| } | ||
| ); | ||
| }, | ||
| }, | ||
| }; |
| 'use strict'; | ||
| var qflow = require('./index'); | ||
| module.exports = { | ||
| 'repeatUntil 500k': function(t) { | ||
| var n = 0; | ||
| t.expect(2); | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| n++; | ||
| cb(null, n >= 500000); | ||
| }, | ||
| function(err, flag) { | ||
| t.equal(flag, true); | ||
| t.equal(n, 500000); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| }; |
+1
-1
@@ -1,1 +0,1 @@ | ||
| module.exports = require('./lib/qflow'); | ||
| module.exports = require('./aflow'); |
+3
-3
| { | ||
| "name": "aflow", | ||
| "version": "0.10.0", | ||
| "version": "0.10.1", | ||
| "description": "async flow control for calls with callbacks", | ||
@@ -19,3 +19,3 @@ "license": "Apache-2.0", | ||
| "scripts": { | ||
| "test": "nodeunit" | ||
| "test": "qnit test-*" | ||
| }, | ||
@@ -41,4 +41,4 @@ "keywords": [ | ||
| "devDependencies": { | ||
| "nodeunit": "0.9.0" | ||
| "qnit": "0.11.0" | ||
| } | ||
| } |
+5
-0
@@ -167,2 +167,7 @@ aflow | ||
| 0.10.1 | ||
| - global.setImmediate(f) is more reliable as function(){ setImmediate(f) } | ||
| - flatten source tree | ||
| 0.10.0 | ||
@@ -169,0 +174,0 @@ |
-220
| /** | ||
| * Flow control library to simplify sequencing calls with callbacks. | ||
| * | ||
| * Similar to async, but faster and more robust. Notable differences: | ||
| * - no more "call stack exceeded" from callbacks | ||
| * - callbacks must be called | ||
| * - callbacks can use process.nextTick without recursion | ||
| * - loop overhead is less | ||
| * - waterfall and series are combined | ||
| * | ||
| * Copyright (C) 2014 Andras Radics | ||
| * Licensed under the Apache License, Version 2.0 | ||
| * | ||
| * 2014-09-28 - AR. | ||
| */ | ||
| module.exports.repeatUntil = repeatUntil; | ||
| module.exports.applyVisitor = applyVisitor; | ||
| module.exports.repeatWhile = repeatWhile; | ||
| module.exports.map = map; | ||
| module.exports.filter = filter; | ||
| module.exports.reduce = reduce; | ||
| module.exports.iterate = module.exports.sequence = iterate; | ||
| // aliases | ||
| module.exports.qflow = iterate; | ||
| module.exports.flow = iterate; | ||
| module.exports.select = filter; | ||
| module.exports.series = iterate; | ||
| module.exports.whilst = repeatWhile; | ||
| var qflowSetImmediate = global.setImmediate || process.nextTick | ||
| /** | ||
| * Repeatedly call func until it signals stop (returns truthy) or err. | ||
| * Func is passed just a standard callback taking err and ret. | ||
| * Returns to the callback the error or the truthy value from func. | ||
| */ | ||
| function repeatUntil( func, callback ) { | ||
| 'use strict'; | ||
| callback = callback || function() {}; | ||
| var alreadyReturned; | ||
| var callDepth = 0; | ||
| function _invokeCallback( err, stop ) { | ||
| if (err || stop) { | ||
| if (alreadyReturned) throw new Error("callback already called"); | ||
| alreadyReturned = true; | ||
| return callback(err, stop); | ||
| } | ||
| else if (++callDepth < 40) { | ||
| _loop(); | ||
| } | ||
| else { | ||
| callDepth = 0; | ||
| qflowSetImmediate(_loop); | ||
| } | ||
| } | ||
| function _loop( ) { | ||
| try { | ||
| func( _invokeCallback ); | ||
| } | ||
| catch (err) { | ||
| // do not inject errors from within the callback back into the callback. | ||
| // If func returns without yielding, the callback will still be in | ||
| // our try/catch block. Re-throw that error like setImmediate would do. | ||
| // But if func has not returned yet, the error is from func, so return it. | ||
| if (alreadyReturned) throw err; else _invokeCallback(err); | ||
| } | ||
| } | ||
| _loop(); | ||
| } | ||
| /** | ||
| * Repeatedly call func as long as test returns truthy. | ||
| */ | ||
| function repeatWhile( test, func, callback ) { | ||
| repeatUntil( | ||
| function _loop(cb) { | ||
| if (test()) func(function(err) { cb(err, false); }); | ||
| else cb(null, true); | ||
| }, | ||
| callback | ||
| ); | ||
| } | ||
| /** | ||
| * visitor pattern: present all items to the visitor function. | ||
| * Returns just error status, no data; to capture the results, | ||
| * wrapper the visitor function. | ||
| */ | ||
| function applyVisitor( dataItems, visitorFunction, callback ) { | ||
| 'use strict'; | ||
| if (!dataItems || typeof dataItems.length !== 'number') { | ||
| return callback(new Error("expected a data array, but got " + typeof data)); | ||
| } | ||
| var next = 0; | ||
| repeatUntil( | ||
| function applyFunc(cb) { | ||
| if (next >= dataItems.length) return cb(null, true); | ||
| visitorFunction(dataItems[next++], function(err) { | ||
| cb(err, err); | ||
| }); | ||
| }, | ||
| function(err, stop) { | ||
| callback(err); | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Run the list of functions, calling each one in turn. | ||
| * Each function is passed a callback and the data item | ||
| * returned by the previous function. Errors interrupt the flow. | ||
| * The final result is the last result generated by the sequence | ||
| * (and not all the individual results). | ||
| */ | ||
| function iterate( functionList, callback ) { | ||
| 'use strict'; | ||
| var lastResult, lastResult2; | ||
| var callReturned; | ||
| applyVisitor( | ||
| functionList, | ||
| function visitor(func, cb) { | ||
| // send this computed result right back to the visitor loop, | ||
| // 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, ret2) { | ||
| callReturned = true; | ||
| lastResult = ret; | ||
| lastResult2 = ret2; | ||
| cb(err, ret, ret2); | ||
| }, | ||
| lastResult, | ||
| lastResult2 | ||
| ); | ||
| }, | ||
| function(err, ret) { | ||
| // return the very last result computed, that applyVisitor returns | ||
| return callback ? (callReturned ? callback(err, lastResult, lastResult2) : callback(err)) : null; | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * 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(). | ||
| */ | ||
| function reduce( data, runningTotal, combinerFunc, callback ) { | ||
| 'use strict'; | ||
| applyVisitor( | ||
| data, | ||
| function visitor(item, cb) { | ||
| combinerFunc(runningTotal, item, function(err, ret) { | ||
| // the running total is the combined results of item and the | ||
| // previous subtotal, as computed by func(). | ||
| if (!err || ret !== undefined) runningTotal = ret; | ||
| return cb(err, runningTotal); | ||
| }); | ||
| }, | ||
| function(err, ret) { | ||
| callback(err, runningTotal); | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Apply the function func to all the items in data, and return the | ||
| * results produced. Func must take a single argument and a callback. | ||
| * In case of error returns the partial results so far; | ||
| * the very last returned result is the one that errored out. | ||
| */ | ||
| function map( data, mapperFunc, callback ) { | ||
| // TODO: might also want to map properties of an object, not just arrays | ||
| // Approach: convert object to list, map list, de-convert results. | ||
| var mappedResults = []; | ||
| applyVisitor( | ||
| data, | ||
| function visitor(item, cb) { | ||
| mapperFunc(item, function(err, mappedItem) { | ||
| if (!err || mappedItem !== undefined) mappedResults.push(mappedItem); | ||
| cb(err); | ||
| }); | ||
| }, | ||
| function(err, ret) { | ||
| return callback(err, mappedResults);; | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * 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. | ||
| */ | ||
| function filter( data, filterFunc, callback ) { | ||
| var pickedItems = []; | ||
| applyVisitor( | ||
| data, | ||
| function visitor(item, cb) { | ||
| filterFunc(item, function(err, yesno) { | ||
| if (yesno) pickedItems.push(item); | ||
| cb(err); | ||
| }); | ||
| }, | ||
| function(err) { | ||
| callback(err, pickedItems); | ||
| } | ||
| ); | ||
| } |
| 'use strict'; | ||
| var qflow = require('../index'); | ||
| function uniqid() { | ||
| return Math.floor(Math.random() * 0x1000000).toString(16); | ||
| } | ||
| module.exports = { | ||
| 'should include file': function(t) { | ||
| t.ok(qflow.repeatUntil); | ||
| t.done(); | ||
| }, | ||
| 'should parse package.json': function(t) { | ||
| require('../package.json'); | ||
| t.done(); | ||
| }, | ||
| 'repeatUntil': { | ||
| 'should trap errors and return them in callback': function(t) { | ||
| t.expect(2); | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| throw new Error("die"); | ||
| }, | ||
| function(err, last) { | ||
| t.ok(err instanceof Error); | ||
| t.equal(err.message, "die"); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| 'should stop looping when visitor returns truthy': function(t) { | ||
| var nloops = 0; | ||
| var id = uniqid(); | ||
| t.expect(2); | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(false, ++nloops >= 10 ? id : false); | ||
| }, | ||
| function(err, last) { | ||
| t.equal(last, id, "expected to see value that stopped loop"); | ||
| t.equal(nloops, 10, "expected 10 loopp iterations"); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| 'should not overflow stack': function(t) { | ||
| t.expect(1); | ||
| var nloops = 0; | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(false, ++nloops > 20000); | ||
| }, | ||
| function(err, last) { | ||
| t.ok(nloops == 20001); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| 'should re-throw subsequent func errors': function(t) { | ||
| t.expect(2); | ||
| try { | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(new Error("first error")); | ||
| throw new Error("second error"); | ||
| }, | ||
| function(err) { | ||
| t.equal(err.message, "first error"); | ||
| } | ||
| ); | ||
| } | ||
| catch (err) { | ||
| t.equal(err.message, "second error"); | ||
| t.done(); | ||
| } | ||
| }, | ||
| 'should re-throw callback errors': function(t) { | ||
| t.expect(1); | ||
| try { | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| cb(null, true); | ||
| }, | ||
| function(err) { | ||
| throw new Error("callback error"); | ||
| } | ||
| ); | ||
| } | ||
| catch (err) { | ||
| t.equal(err.message, "callback error"); | ||
| 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(); | ||
| } | ||
| ); | ||
| }, | ||
| }, | ||
| }; |
| 'use strict'; | ||
| var qflow = require('../index'); | ||
| module.exports = { | ||
| 'repeatUntil 500k': function(t) { | ||
| var n = 0; | ||
| t.expect(2); | ||
| qflow.repeatUntil( | ||
| function(cb) { | ||
| n++; | ||
| cb(null, n >= 500000); | ||
| }, | ||
| function(err, flag) { | ||
| t.equal(flag, true); | ||
| t.equal(n, 500000); | ||
| t.done(); | ||
| } | ||
| ); | ||
| }, | ||
| }; |
AI-detected possible typosquat
Supply chain riskAI has identified this package as a potential typosquat of a more popular package. This suggests that the package may be intentionally mimicking another package's name, description, or other metadata.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
26480
0.5%200
2.56%2
Infinity%