diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30bc162 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..9937050 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +CHANGES +examples +test +.npmignore +.travis.yml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..540b3a6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" + - "0.11" +after_script: "npm install coveralls && npm run-script test-lcov | ./node_modules/.bin/coveralls" \ No newline at end of file diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..ed8b428 --- /dev/null +++ b/CHANGES @@ -0,0 +1,17 @@ +v0.1.5 (25 Jan 2015) + Bump version. + +v0.1.4 (19 Jan 2015) + Added a "baton" parameter. + +v0.1.3 (12 Jan 2015) + Bump version. + +v0.1.2 (10 Jan 2015) + Bump version. + +v0.1.1 (10 Jan 2015) + Bump version. + +v0.1.0 (09 Jan 2015) + First release. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5c57da6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Gabriel Llamas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..634c4bd --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +cargo-ship +========== + +#### Parallel execution of tasks with a shared namespace #### + +[![npm version][npm-version-image]][npm-url] +[![Travis][travis-image]][travis-url] +[![Coveralls][coveralls-image]][coveralls-url] + +The extremly well-known parallel execution of tasks, but with a cargo, a shared object where tasks can store data. It's like a cargo ship, cranes (tasks) storing data (cargo). Each task writing to the shared object. + +It's very useful when you need to call a series of functions in parallel and store the data in a common place. + +```javascript +var cranes = [ + function (cargo, done) { + cargo.a = 1; + done(); + }, + function (cargo, done) { + cargo.b = 2; + done(); + }, + function (cargo, done) { + cargo.c = 3; + done(); + } +]; + +ship.load(cranes, function (err, cargo) { + // cargo { a: 1, b: 2, c: 3 } +}); +``` + +It's basically the same behaviour as the `async.parallel()` with with a slightly and sightly! interface. + +___module_.load(cranes[, cargo], callback) : undefined__ +Executes all tasks in parallel. + +`cranes` is an array of functions to run in parallel. Each function has the signature `function(cargo, done)`, where `cargo` is the shared object and `done` the function to call when the task finishes. As usual, pass an error to `done()` to abort the execution of the tasks. This is the error returned by the `load()` function. Because aborting asynchronous parallel tasks is not possible once they begin, the callback is guaranteed to be called only once with the first error occurred. + +A `cargo` can be passed from outside. Use the second parameter to initialize the cargo with data. + +```javascript +var cranes = [ + function (cargo, done) { + cargo.b = 2; + done(); + }, + function (cargo, done) { + cargo.c = 3; + done(); + } +]; + +ship.load(cranes, { a: 1 }, function (err, cargo) { + // cargo { a: 1, b: 2, c: 3 } +}); +``` + +[npm-version-image]: https://img.shields.io/npm/v/cargo-ship.svg?style=flat +[npm-url]: https://npmjs.org/package/cargo-ship +[travis-image]: https://img.shields.io/travis/gagle/node-cargo-ship.svg?style=flat +[travis-url]: https://travis-ci.org/gagle/node-cargo-ship +[coveralls-image]: https://img.shields.io/coveralls/gagle/node-cargo-ship.svg?style=flat +[coveralls-url]: https://coveralls.io/r/gagle/node-cargo-ship \ No newline at end of file diff --git a/examples/boot/index.js b/examples/boot/index.js new file mode 100644 index 0000000..4e5a376 --- /dev/null +++ b/examples/boot/index.js @@ -0,0 +1,11 @@ +'use strict'; + +var race = require('../../lib'); + +race.start(require('./modules').map(function (name) { + return require('./modules/' + name); +}), function (err, baton) { + if (err) return console.error(err); + console.log(baton); + // { a: 1, b: 2, c: 3 } +}); \ No newline at end of file diff --git a/examples/boot/modules/a.js b/examples/boot/modules/a.js new file mode 100644 index 0000000..daae5ee --- /dev/null +++ b/examples/boot/modules/a.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = function (baton, next) { + baton.a = 1; + next(); +}; \ No newline at end of file diff --git a/examples/boot/modules/b.js b/examples/boot/modules/b.js new file mode 100644 index 0000000..8800fe7 --- /dev/null +++ b/examples/boot/modules/b.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = function (baton, next) { + baton.b = 2; + next(); +}; \ No newline at end of file diff --git a/examples/boot/modules/c.js b/examples/boot/modules/c.js new file mode 100644 index 0000000..dad207a --- /dev/null +++ b/examples/boot/modules/c.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = function (baton, next) { + baton.c = 3; + next(); +}; \ No newline at end of file diff --git a/examples/boot/modules/index.js b/examples/boot/modules/index.js new file mode 100644 index 0000000..2969c3e --- /dev/null +++ b/examples/boot/modules/index.js @@ -0,0 +1,7 @@ +'use strict'; + +module.exports = [ + 'a', + 'b', + 'c' +]; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..1dd4961 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,29 @@ +'use strict'; + +exports.load = function (cranes, cargo, cb) { + if (arguments.length === 2) { + cb = cargo; + cargo = {}; + } + + var len = cranes.length; + var remaining = len; + if (!len) return cb(null, cargo); + + var done = function (err) { + if (errored) return; + + if (err) { + errored = true; + return cb(err); + } + + if (!--remaining) return cb(null, cargo); + }; + + var errored = false; + + for (var i = 0; !errored && i < len; i++) { + cranes[i](cargo, done); + } +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..dd5da66 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "cargo-ship", + "version": "0.0.0", + "description": "Parallel execution of tasks with a shared namespace", + "keywords": [ + "parallel", + "tasks", + "shared" + ], + "author": "Gabriel Llamas ", + "repository": "git://github.com/gagle/node-cargo-ship.git", + "engines": { + "node": ">=0.10" + }, + "devDependencies": { + "code": "^1.3.0", + "lab": "^5.2.1", + "sinon": "^1.12.2" + }, + "scripts": { + "test": "node node_modules/lab/bin/lab -t 100", + "test-lcov": "node node_modules/lab/bin/lab -t 100 -r lcov" + }, + "license": "MIT", + "main": "lib" +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..ab149a1 --- /dev/null +++ b/test/index.js @@ -0,0 +1,154 @@ +'use strict'; + +var sinon = require('sinon'); +var code = require('code'); +var lab = module.exports.lab = require('lab').script(); + +var expect = code.expect; +var describe = lab.describe; +var it = lab.it; + +var ship = require('../lib'); + +describe('cargo-ship', function () { + it('calls tasks in parallel (sync)', function (done) { + var cranes = [ + function (cargo, done) { + expect(cargo).to.deep.equal({}); + cargo.a = 1; + done(); + }, + function (cargo, done) { + expect(cargo).to.deep.equal({ + a: 1 + }); + cargo.b = 2; + done(); + }, + function (cargo, done) { + expect(cargo).to.deep.equal({ + a: 1, + b: 2 + }); + cargo.a = 3; + done(); + } + ]; + + ship.load(cranes, function (err, cargo) { + expect(err).to.not.exist(); + expect(cargo).to.deep.equal({ + a: 3, + b: 2 + }); + done(); + }); + }); + + it('calls tasks in parallel (async)', function (done) { + var cranes = [ + function (cargo, done) { + cargo.a = 1; + process.nextTick(done); + }, + function (cargo, done) { + cargo.b = 2; + process.nextTick(done); + }, + function (cargo, done) { + cargo.c = 3; + process.nextTick(done); + } + ]; + + ship.load(cranes, function (err, cargo) { + expect(err).to.not.exist(); + expect(cargo).to.deep.equal({ + a: 1, + b: 2, + c: 3 + }); + done(); + }); + }); + + it('finishes with no tasks', function (done) { + ship.load([], function (err, cargo) { + expect(err).to.not.exist(); + expect(cargo).to.deep.equal({}); + done(); + }); + }); + + it('can receive a cargo from the outside', function (done) { + var cranes = [ + function (cargo, done) { + expect(cargo).to.deep.equal({ + c: 3 + }); + cargo.a = 1; + done(); + }, + function (cargo, done) { + cargo.b = 2; + done(); + } + ]; + + ship.load(cranes, { c: 3 }, function (err, cargo) { + expect(err).to.not.exist(); + expect(cargo).to.deep.equal({ + a: 1, + b: 2, + c: 3 + }); + done(); + }); + }); + + it('aborts with error (sync)', function (done) { + var errInstance = new Error(); + var spy = sinon.spy(function (baton, next) { + next(); + }); + + var cranes = [ + function (cargo, done) { + done(errInstance); + }, + spy + ]; + + ship.load(cranes, function (err, cargo) { + expect(err).to.equal(errInstance); + expect(cargo).to.be.undefined(); + expect(spy.callCount).to.equal(0); + done(); + }); + }); + + it('aborts with error (async)', function (done) { + var errInstance = new Error(); + var errInstance2 = new Error(); + + var spyCrane = sinon.spy(function (baton, next) { + next(errInstance); + }); + + var cranes = [ + function (cargo, done) { + process.nextTick(function () { + done(errInstance2); + }); + }, + spyCrane + ]; + + ship.load(cranes, function (err, cargo) { + expect(err).to.equal(errInstance); + expect(cargo).to.be.undefined(); + expect(spyCrane.callCount).to.equal(1); + done(); + }); + }); +}); \ No newline at end of file