Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Series next tick rework map each added #3

Merged
merged 10 commits into from
Jun 20, 2016
55 changes: 41 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,52 @@
# palinode
NodeJS callback-based flow control utility library
NodeJS callback-based flow control utility library. Palinode focuses on a pure, dependency free, no-frills, native javascript implementation. Palinode is intentionally not compatible with browsers; it is intended for NodeJS only.

[![Build Status](https://travis-ci.org/GannettDigital/palinode.svg?branch=master)](https://travis-ci.org/GannettDigital/palinode) [![Coverage Status](https://coveralls.io/repos/github/GannettDigital/palinode/badge.svg?branch=master)](https://coveralls.io/github/GannettDigital/palinode?branch=master)

palinode (noun): a poem in which the poet retracts a view or sentiment expressed in a former poem. - source: Google.

## Installation
```Shell
npm install palinode
```
## Test

## Test
```Shell
npm run test
```

## Coverage

```Shell
npm run cover-html
```

## Setup
```Javascript
var theFunctionToUse = require(`palinode`).theFunctionYouWant
```

Just require `palinode`
## Callback Pattern Expectations
As with the vast majority of native node libraries, all callbacks are by convention expected to be in the form:
```Javascript
function(error [, param1, param2, param3...]) {}
```
- A callback invoked with a falsy value in the 0th parameter position indicates that caller was successful.
- A callback invoked with a truthy value in the 0th parameter position indicates the error encountered by the caller.
- As a best practice, errors provided as the 0th parameter should be an instance of the `Error` prototype.
- Parameters in the 1st-nth positions contain successful response information, if any.

## Supported methods

### Series
Runs a series of functions. Each function calls back to the next. Any parameters passed to a callback are spread into the subsequent function call.
All functions in a series should accept a callback in the form:
```
function(error, param1[, param2, param3...]) {}
```
Example usage
```
Runs a series of functions. Each function calls back to the next. Any parameters passed to a callback are spread into the subsequent function call.
The provided array of functions is not mutated.

#### Example usage
```Javascript
var series = require('palinode').series;

var numToAddEachTime = 5;
function add(a, b, callback) {
if (a < 0) {
callback(new Error('a cannot be less than zero'));
}
callback(null, a + b, numToAddEachTime);
}

Expand All @@ -55,3 +63,22 @@ series(functions, function(error, result) {
}
```

### Map Each
Runs a single function against each member of an array, invoking a callback when complete or when an error occurs during execution. If successful for each item in the input array, the result provided to the callback will be a new array containing the mapped values. If an error occurs, no data will be returned in the callback result.
The input array is not mutated.

#### Example usage
```Javascript
var mapEach = require('palinode').mapEach;
var inputArray = [1,2,3,4,5,6,7,8,9,10];

functon square(input, callback) {
return callback(null, input * input);
}

mapEach(inputArray, square, function(error, result) {
console.log(result);
//outputs: [1,4,9,16,25,36,49,64,81,100]
});

```
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

module.exports = {
series: require('./lib/series.js').series
series: require('./lib/series.js').series,
mapEach: require('./lib/map-each.js').mapEach
};
25 changes: 25 additions & 0 deletions lib/map-each.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

var MapEach;
module.exports = MapEach = {

mapEach: function(inputArray, forEachMethod, callback) {
var mapEachCallback = MapEach._mapEachCallback.bind(null, callback, inputArray, forEachMethod, 0, []);
process.nextTick(forEachMethod.bind(null, inputArray[0], mapEachCallback));
},
_mapEachCallback: function(allDone, inputArray, forEachMethod, index, results, error, result) {
if (error) {
return process.nextTick(allDone.bind(null, error));
}

results.push(result);
++index;

if (index === inputArray.length) {
return process.nextTick(allDone.bind(null, null, results));
}

var mapEachCallback = MapEach._mapEachCallback.bind(null, allDone, inputArray, forEachMethod, index, results);
process.nextTick(forEachMethod.bind(null, inputArray[index], mapEachCallback));
}
};
9 changes: 6 additions & 3 deletions lib/series.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
'use strict';

var Series;
module.exports = Series = {

series: function(functions, callback) {
functions[0](Series._seriesCallback.bind(null, callback, functions, 1));
var seriesCallback = Series._seriesCallback.bind(null, callback, functions, 1);
process.nextTick(functions[0].bind(null, seriesCallback));
},

_seriesCallback: function(allDone, functions, index, error) {
var slicedArgs = Array.prototype.slice.call(arguments);
var results = slicedArgs.slice(4);

if (error) return allDone(error);
if (error) return process.nextTick(allDone.bind(null, error));

if (functions.length === index) {
results.unshift(null);
allDone.apply(null, results);
var done = Function.prototype.apply.bind(allDone, null, results);
process.nextTick(done);
return;
}

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"istanbul": "0.4.3",
"jscs": "3.0.4",
"mocha": "2.4.5 ",
"mocha-teamcity-reporter": "1.0.0",
"sinon": "1.17.4"
},
"scripts": {
Expand Down
38 changes: 38 additions & 0 deletions test/unit/map-each-practical-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

var expect = require('chai').expect;
var sinon = require('sinon');

describe('map-each - practical test', function() {
var MapEach;
var inputArray = [1,2,3,4,5,6,7,8,9,10];

var squareSpy;
function square(input, callback) {
callback(null, input * input);
}

before(function() {
MapEach = require('../../lib/map-each.js');
squareSpy = sinon.spy(square);
});

var error;
var result;
beforeEach(function(done) {
squareSpy.reset();
MapEach.mapEach(inputArray, squareSpy, function(err, res) {
error = err;
result = res;
done();
});
});

it('should execute the provided callback for each element in the input array', function() {
expect(result).to.eql([1,4,9,16,25,36,49,64,81,100]);
});

it('should call the iteratee once for each member of the input array', function() {
expect(squareSpy.callCount).to.equal(inputArray.length);
});
});
138 changes: 138 additions & 0 deletions test/unit/map-each.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
'use strict';

var sinon = require('sinon');
var expect = require('chai').expect;

describe('map-each - unit tests', function() {

var MapEach;
var nextTickStub;
var inputArray = [1,2,3,4,5];
var iteratee;
var iterateeBindStub;
var iterateeBindStubResult = function iterateeBindResult() {};;

before(function() {
MapEach = require('../../lib/map-each.js');
nextTickStub = sinon.stub(process, 'nextTick');
iteratee = sinon.spy();
iterateeBindStub = sinon.stub(iteratee, 'bind').returns(iterateeBindStubResult);
});

beforeEach(function() {
nextTickStub.reset();
iterateeBindStub.reset();
});

after(function() {
process.nextTick.restore();
});

describe('map-each - entry-point', function() {
var mapEachCallbackStub;
var mapEachCallbackBindStub;
var callback = function callback() {};
var mapEachCallbackBindResult = function mapEachCallbackBindResult() {};

before(function() {
mapEachCallbackStub = sinon.stub(MapEach, '_mapEachCallback');
mapEachCallbackBindStub = sinon.stub(mapEachCallbackStub, 'bind').returns(mapEachCallbackBindResult);
});

beforeEach(function() {
iterateeBindStub.reset();
mapEachCallbackStub.reset();
MapEach.mapEach(inputArray, iteratee, callback);
});

after(function() {
MapEach._mapEachCallback.restore();
});

it('should bind the callback, input array, iteratee, index and accumulator to the mapEachCallback', function() {
expect(mapEachCallbackBindStub.args[0]).eql([
null, callback, inputArray, iteratee, 0, []
]);
});

it('should bind the first element of the input array, and the bound callback to the iteratee', function() {
expect(iterateeBindStub.args[0]).to.eql([
null, inputArray[0], mapEachCallbackBindResult
]);
});

it('should invoke provide the bound iteratee function to process.nextTick', function() {
expect(nextTickStub.args[0]).to.eql([
iterateeBindStubResult
]);
});

it('should invoke process.nextTick once', function() {
expect(nextTickStub.callCount).to.equal(1);
});
});

describe('map-each - callback', function() {
var allDoneStub;
var allDoneBindStub;
var allDoneBindResult = function allDoneBound() {};

before(function() {
allDoneStub = sinon.stub();
allDoneBindStub = sinon.stub(allDoneStub, 'bind').returns(allDoneBindResult);
});

beforeEach(function() {
allDoneBindStub.reset();
});

describe('when invoked with an error', function() {

var expectedError = new Error('not making any promises');

beforeEach(function() {
MapEach._mapEachCallback(allDoneStub, inputArray, iteratee, 0, [], expectedError);
});

it('should bind the error to the allDone callback', function() {
expect(allDoneBindStub.args[0]).to.eql([
null, expectedError
]);
});

it('should pass the bound allDone callback to process.nextTick', function() {
expect(nextTickStub.args[0]).to.eql([allDoneBindResult]);
});

it('should invoke process.nextTick once', function() {
expect(nextTickStub.callCount).to.equal(1);
});
});

describe('when invoked for last callback of the iteratee', function() {
beforeEach(function() {
MapEach._mapEachCallback(allDoneStub, inputArray, iteratee, 4, ['yay', 'res', 'u', 'lts'], null, 'here');
});

it('should bind null and the results array to the allDone callback', function() {
expect(allDoneBindStub.args[0]).to.eql([
null, null, ['yay', 'res', 'u', 'lts', 'here']
]);
});

it('should pass the bound allDone callback to process.nextTick', function() {
expect(nextTickStub.args[0]).to.eql([allDoneBindResult]);
});

it('should invoke process.nextTick once', function() {
expect(nextTickStub.callCount).to.equal(1);
});
});

describe('when invoked with the n-1th array element', function() {
beforeEach(function() {
MapEach._mapEachCallback(allDoneStub, inputArray, iteratee, 3, ['yay', 'res', 'u'], null, 'lts');
});
});
});
});
53 changes: 53 additions & 0 deletions test/unit/series-practical-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

var expect = require('chai').expect;
var sinon = require('sinon');

describe('series - practical test', function() {
var Series;
var functionSeries = [];
var initialA;
var initialB;
var numTimesToAdd;
var numToAddEachTime;

var addSpy;
function add(a, b, callback) {
callback(null, a + b, numToAddEachTime);
}

before('set up input', function() {
Series = require('../../lib/series.js');
initialA = 2;
initialB = 0;
numTimesToAdd = 10;
numToAddEachTime = 5;

addSpy = sinon.spy(add);

functionSeries.push(addSpy.bind(null, initialA, initialB));
for (var i = 0; i < numTimesToAdd; ++i) {
functionSeries.push(addSpy);
}
});

var error;
var result;
beforeEach(function(done) {
addSpy.reset();
Series.series(functionSeries, function(err, res) {
error = err;
result = res;
done();
});
});

it('should execute all the functions provided and call the callback', function() {
expect(result).to.equal(numToAddEachTime * numTimesToAdd + (initialA + initialB));
});

it('should invoke the each method in the series once', function() {
expect(addSpy.callCount).to.equal(functionSeries.length);
});
});

Loading