diff --git a/lib/jam.js b/lib/jam.js index 067be9c..7e8aace 100644 --- a/lib/jam.js +++ b/lib/jam.js @@ -131,22 +131,22 @@ module.exports = (function() { } }; - // ## jam.map( array, iterator( next, element, index ) ) - - // Execute the given `iterator` for each element given in the `array`. The iterator is - // given a `next` function and the element to act on. + // ## jam.each( array, iterator( next, element, index ) ) + + // Execute the given `iterator` function for each element given in the `array`. The + // iterator is given a `next` function and the element to act on. // // You can also pass `arguments` and `"strings"` as an array. // // Under the hood, a JAM step is added for each element. So the iterator will be // called serially, one after another finish. A parallel version maybe added in the // future. - func.map = function(array, iterator) { + func.each = function(array, iterator) { ensureArray(array, 'array'); ensureFunc(iterator, 'iterator'); return function(next) { - // Builds another JAM chain internally. + // Builds another JAM chain internally var chain = jam(jam.identity) , count = array.length; @@ -158,6 +158,39 @@ module.exports = (function() { }; }; + // ## jam.map( array, iterator( next, element, index ) ) + + // Works exactly like the `each` helper but if a value is passed to the iterator's + // `next` function, it is collected into a new array which will be passed to the next + // function in the JAM chain after `map`. + func.map = function(array, iterator) { + ensureArray(array, 'array'); + ensureFunc(iterator, 'iterator'); + + return function(next) { + // Builds another JAM chain internally and collect results. + // TODO: Dry with .each? + var chain = jam(jam.identity) + , count = array.length + , result = []; + + for (var i = 0; i < count; i++) (function(element, i) { + chain = chain(function(next, previous) { + result.push(previous); + iterator(next, element, i); + }); + })(array[i], i); + + chain = chain(function(next, last) { + result.push(last); + result.shift(); // discard first undefined element + next(null, result); + }); + + return chain(next); + }; + }; + // ## jam.timeout( timeout ) // Pauses the chain for the specified `timeout` using `setTimeout`. Useful for diff --git a/test/helpers.js b/test/helpers.js index 7d93e2c..fece330 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -9,6 +9,8 @@ , NON_ARRAYS = [undefined, null, 123, 'string', { }, function() { }] , NON_NUMS = [undefined, null, 'string', { }, function() { }, []]; + var ITERABLES = ['each', 'map']; + // functions with multiple usage modes describeModes = function(modes, tests) { @@ -141,52 +143,6 @@ }); }); // .call - describe('.map function', function() { - it('should be exported', function() { - assert.typeOf(this.jam.map, 'function'); - }); - - it('should throws if array argument missing or not an array', function() { - var me = this; - NON_ARRAYS.forEach(function(thing) { - assert.throws(function() { me.jam.map(thing); }, /array/i); - }); - }); - - it('should throws if iterator argument missing or not a function', function() { - var me = this; - NON_FUNCTIONS.forEach(function(thing) { - assert.throws(function() { me.jam.map([1,2,3], thing); }, /iterator/i); - }); - }); - - it('should calls iterator for each element in the given array serially', function(done) { - var elements = ['one', 'two', 'three'] - , index = 0; - - this.jam(this.jam.map(elements, function(next, element, index_) { - assert.equal(index_, index); - assert.equal(element, elements[index]); - index++; - next(); - }))(done); - }); - - it('should forwards error properly when an iterator calls `next()` with an Error`', function(done) { - var err = new Error('test error') - , elements = ['one', 'two', 'three'] - , index = 0; - - this.jam(this.jam.map(elements, function(next, element, index_) { - next((index_ < 2) ? undefined : err); // throw on last element - - }))(function(e) { - assert.equal(e, err); // should be forwarded here - done(); - }); - }); - }); // .map - describe('.timeout function', function() { it('should be exported', function() { assert.typeOf(this.jam.timeout, 'function'); @@ -214,6 +170,73 @@ }); }); + describe('iterable functions', function() { + ITERABLES.forEach(function(func) { + + describe('.' + func + ' function', function() { + it('should be exproted', function() { + assert.typeOf(this.jam[func], 'function'); + }); + + it('should throws if array argument missing or not an array', function() { + var me = this; + NON_ARRAYS.forEach(function(thing) { + assert.throws(function() { me.jam[func](thing); }, /array/i); + }); + }); + }); + + it('should throws if iterator argument missing or not a function', function() { + var me = this; + NON_FUNCTIONS.forEach(function(thing) { + assert.throws(function() { me.jam[func]([1,2,3], thing); }, /iterator/i); + }); + }); + + it('should calls iterator for each element in the given array serially', function(done) { + var elements = ['one', 'two', 'three'] + , index = 0; + + this.jam(this.jam[func](elements, function(next, element, index_) { + assert.equal(index_, index); + assert.equal(element, elements[index]); + index++; + next(); + }))(done); + }); + + it('should forwards error properly when an iterator calls `next()` with an Error`', function(done) { + var err = new Error('test error') + , elements = ['one', 'two', 'three'] + , index = 0; + + this.jam(this.jam[func](elements, function(next, element, index_) { + next((index_ < 2) ? undefined : err); // throw on last element + + }))(function(e) { + assert.equal(e, err); // should be forwarded here + done(); + }); + }); + + }); + }); // iterables + + describe('.map function', function() { + it('should also returns collected results passed to next()', function(done) { + var elements = [1, 2, 3]; + + this.jam(this.jam.map(elements, function(next, element, index) { + return next(null, element * 2); + }))(function(next, result) { + for (var i = 0; i < elements.length; i++) { + assert.equal(elements[i] * 2, result[i]); + } + next() + })(done); + }); + }); + }); })();