Skip to content

Commit

Permalink
Adds .each and makes .map also collect result.
Browse files Browse the repository at this point in the history
  • Loading branch information
Chakrit Wichian committed Aug 18, 2013
1 parent e1773e8 commit 2cf70c1
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 52 deletions.
45 changes: 39 additions & 6 deletions lib/jam.js
Expand Up @@ -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;

Expand All @@ -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
Expand Down
115 changes: 69 additions & 46 deletions test/helpers.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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);
});
});

});

})();

0 comments on commit 2cf70c1

Please sign in to comment.