![contra.png][logo]
[![badge](https://travis-ci.org/bevacqua/contra.png?branch=master)](https://travis-ci.org/bevacqua/contra) [![badge](https://badge.fury.io/js/contra.png)](http://badge.fury.io/js/contra) [![badge](https://badge.fury.io/bo/contra.png)](http://badge.fury.io/bo/contra) [![help me on gittip](http://gbindex.ssokolow.com/img/gittip-43x20.png)](https://www.gittip.com/bevacqua/) [![flattr.png](https://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=nzgb&url=https%3A%2F%2Fgithub.com%2Fbevacqua%2Fcontra)
> Asynchronous flow control with a functional taste to it
`λ` aims to stay small and simple, while powerful. Inspired by [async][1] and [lodash][2]. Methods are implemented individually and not as part of a whole. That design helps when considering to export functions individually. If you need all the methods in `async`, then stick with it. Otherwise, you might want to check `λ` out!
Feature requests will be considered on a case-by-case basis.
#### Quick Links
- [CHANGELOG](CHANGELOG.md)
- [Comparison with `async`](#comparison-with-async)
- [Browser Support](#browser-support)
- [License](#License)
#### API
Flow Control
- [`λ.waterfall`](#%CE%BBwaterfalltasks-done)
- [`λ.series`](#%CE%BBseriestasks-done)
- [`λ.concurrent`](#%CE%BBconcurrenttasks-cap-done)
Functional
- [`λ.each`](#%CE%BBeachitems-cap-iterator-done)
- [`λ.each.series`](#%CE%BBeachseriesitems-iterator-done)
- [`λ.map`](#%CE%BBmapitems-cap-iterator-done)
- [`λ.map.series`](#%CE%BBmapseriesitems-iterator-done)
- [`λ.filter`](#%CE%BBfilteritems-cap-iterator-done)
- [`λ.filter.series`](#%CE%BBfilterseriesitems-iterator-done)
Uncategorized
- [`λ.queue`](#%CE%BBqueueworker-cap1)
- [`λ.emitter`](#%CE%BBemitterthing-options)
- [`λ.curry`](#%CE%BBcurryfn-arguments)
# Install
Install using `npm` or `bower`. Or get the [source code][3] and embed that in a `
```
The only reason `contra` isn't published as `λ` directly is to make it easier for you to type.
[_Back to top_](#quick-links)
# API
These are the asynchronous flow control methods provided by `λ`.
## `λ.waterfall(tasks, done?)`
Executes tasks in series. Each step receives the arguments from the previous step.
- `tasks` Array of functions with the `(...results, next)` signature
- `done` Optional function with the `(err, ...results)` signature
```js
λ.waterfall([
function (next) {
next(null, 'params for', 'next', 'step');
},
function (a, b, c, next) {
console.log(b);
// <- 'next'
next(null, 'ok', 'done');
}
], function (err, ok, result) {
console.log(result);
// <- 'done'
});
```
[_Back to top_](#quick-links)
## `λ.concurrent(tasks, cap?, done?)`
Executes tasks concurrently. Results get passed as an array or hash to an optional `done` callback. Task order is preserved in results. You can set a concurrency cap, and it's uncapped by default.
- `tasks` Collection of functions with the `(cb)` signature. Can be an array or an object
- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)
- `done` Optional function with the `(err, results)` signature
```js
λ.concurrent([
function (cb) {
setTimeout(function () {
cb(null, 'boom');
}, 1000);
},
function (cb) {
cb(null, 'foo');
}
], function (err, results) {
console.log(results);
// <- ['boom', 'foo']
});
```
Using objects
```js
λ.concurrent({
first: function (cb) {
setTimeout(function () {
cb(null, 'boom');
}, 1000);
},
second: function (cb) {
cb(null, 'foo');
}
}, function (err, results) {
console.log(results);
// <- { first: 'boom', second: 'foo' }
});
```
[_Back to top_](#quick-links)
## `λ.series(tasks, done?)`
**Effectively an alias for `λ.concurrent(tasks, 1, done?)`.**
Executes tasks in series. `done` gets all the results. Results get passed as an array or hash to an optional `done` callback. Task order is preserved in results.
- `tasks` Collection of functions with the `(next)` signature. Can be an array or an object
- `done` Optional function with the `(err, results)` signature
```js
λ.series([
function (next) {
setTimeout(function () {
next(null, 'boom');
}, 1000);
},
function (next) {
next(null, 'foo');
}
], function (err, results) {
console.log(results);
// <- ['boom', 'foo']
});
```
Using objects
```js
λ.series({
first: function (next) {
setTimeout(function () {
next(null, 'boom');
}, 1000);
},
second: function (next) {
next(null, 'foo');
}
}, function (err, results) {
console.log(results);
// <- { first: 'boom', second: 'foo' }
});
```
[_Back to top_](#quick-links)
## `λ.each(items, cap?, iterator, done?)`
Applies an iterator to each element in the collection concurrently.
- `items` Collection of items. Can be an array or an object
- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)
- `iterator(item, key?, cb)` Function to execute on each item
- `item` The current item
- `key` Optional, array/object key of the current item
- `cb` Needs to be called when processing for current item is done
- `done` Optional function with the `(err)` signature
```js
λ.each({ thing: 900, another: 23 }, function (item, cb) {
setTimeout(function () {
console.log(item);
cb();
}, item);
});
// <- 23
// <- 900
```
[_Back to top_](#quick-links)
## `λ.each.series(items, iterator, done?)`
Effectively an alias for `λ.each(items, 1, iterator, done?)`.
[_Back to top_](#quick-links)
## `λ.map(items, cap?, iterator, done?)`
Applies an iterator to each element in the collection concurrently. Produces an object with the transformation results. Task order is preserved in the results.
- `items` Collection of items. Can be an array or an object
- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)
- `iterator(item, key?, cb)` Function to execute on each item
- `item` The current item
- `key` Optional, array/object key of the current item
- `cb` Needs to be called when processing for current item is done
- `done` Optional function with the `(err, results)` signature
```js
λ.map({ thing: 900, another: 23 }, function (item, cb) {
setTimeout(function () {
cb(null, item * 2);
}, item);
}, function (err, results) {
console.log(results);
<- { thing: 1800, another: 46 }
});
```
[_Back to top_](#quick-links)
## `λ.map.series(items, iterator, done?)`
Effectively an alias for `λ.map(items, 1, iterator, done?)`.
[_Back to top_](#quick-links)
## `λ.filter(items, cap?, iterator, done?)`
Applies an iterator to each element in the collection concurrently. Produces an object with the filtered results. Task order is preserved in results.
- `items` Collection of items. Can be an array or an object
- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)
- `iterator(item, key?, cb)` Function to execute on each item
- `item` The current item
- `key` Optional, array/object key of the current item
- `cb` Needs to be called when processing for current item is done
- `err` An optional error which will short-circuit the filtering process, calling `done`
- `keep` Truthy will keep the item. Falsy will remove it in the results
- `done` Optional function with the `(err, results)` signature
```js
λ.filter({ thing: 900, another: 23, foo: 69 }, function (item, cb) {
setTimeout(function () {
cb(null, item % 23 === 0);
}, item);
}, function (err, results) {
console.log(results);
<- { another: 23, foo: 69 }
});
```
[_Back to top_](#quick-links)
## `λ.filter.series(items, iterator, done?)`
Effectively an alias for `λ.filter(items, 1, iterator, done?)`.
[_Back to top_](#quick-links)
## `λ.queue(worker, cap=1)`
Used to create a job queue.
- `worker(job, done)` Function to process jobs in the queue
- `job` The current job
- `done` Needs to be called when processing for current job is done
- `cap` Optional concurrency level, defaults to `1` (serial)
Returns a queue you can `push` or `unshift` jobs to. You can pause and resume the queue by hand.
- `push(job, done?)` Array of jobs or an individual job object. Enqueue those jobs, continue processing **(unless paused)**. Optional callback to run when each job is completed
- `unshift(job, done?)` Array of jobs or an individual job object. Add jobs to the top of the queue, continue processing **(unless paused)**. Optional callback to run when each job is completed
- `pending` Property. Jobs that haven't started processing yet
- `length` Short-hand for `pending.length`, only works if getters can be defined
- `pause()` Stop processing jobs. Those already being processed will run to completion
- `resume()` Start processing jobs again, after a `pause()`
- `on('drain', fn)` Execute `fn` whenever there's no more pending _(or running)_ jobs and processing is requested. Processing can be requested using `resume`, `push`, or `unshift`
```js
var q = λ.queue(worker);
function worker (job, done) {
console.log(job);
done(null);
}
q.push('job', function () {
console.log('this job is done!');
});
q.push(['some', 'more'], function () {
console.log('one of these jobs is done!');
});
q.on('drain', function () {
console.log('all done!');
// if you enqueue more tasks now, then drain
// will fire again when pending.length reaches 0
});
// <- 'this job is done!'
// <- 'one of these jobs is done!'
// <- 'one of these jobs is done!'
// <- 'all done!'
```
[_Back to top_](#quick-links)
## `λ.emitter(thing={}, options={})`
Augments `thing` with the event emitter methods listed below. If `thing` isn't provided, an event emitter is created for you. Emitter methods return the `thing` for chaining.
- `thing` Optional. Writable JavaScript object
- `emit(type, ...arguments)` Emits an event of type `type`, passing any `...arguments`
- `emitterSnapshot(type)` Returns a function you can call, passing any `...arguments`
- `on(type, fn)` Registers an event listener `fn` for `type` events
- `once(type, fn)` Same as `on`, but the listener is discarded after one callback
- `off(type, fn)` Unregisters an event listener `fn` from `type` events
- `off(type)` Unregisters all event listeners from `type` events
- `off()` Unregisters all event listeners
The `emitterSnapshot(type)` method lets you remove all event listeners before emitting an event that might add more event listeners which shouldn't be removed. In the example below, `thing` removes all events and then emits a `'destroy'` event, resulting in a `'create'` event handler being attached. If we just used `thing.off()` after emitting the destroy event, the `'create'` event handler would be wiped out too _(or the consumer would have to know implementation details as to avoid this issue)_.
```js
var thing = λ.emitter();
thing.on('foo', foo);
thing.on('bar', bar);
thing.on('destroy', function () {
thing.on('create', reinitialize);
});
var destroy = thing.emitterSnapshot('destroy');
thing.off();
destroy();
```
The emitter can be configured with the following options, too.
- `async` Debounce listeners asynchronously. By default they're executed in sequence.
- `throws` Throw an exception if an `error` event is emitted and no listeners are defined. Defaults to `true`.
```js
var thing = λ.emitter(); // also, λ.emitter({ foo: 'bar' })
thing.once('something', function (level) {
console.log('something FIRST TROLL');
});
thing.on('something', function (level) {
console.log('something level ' + level);
});
thing.emit('something', 4);
thing.emit('something', 5);
// <- 'something FIRST TROLL'
// <- 'something level 4'
// <- 'something level 5'
```
Returns `thing`.
Events of type `error` have a special behavior. `λ.emitter` will throw if there are no `error` listeners when an error event is emitted. This behavior can be turned off setting `throws: false` in the options.
```js
var thing = { foo: 'bar' };
λ.emitter(thing);
thing.emit('error', 'foo');
<- throws 'foo'
```
If an `'error'` listener is registered, then it'll work just like any other event type.
```js
var thing = { foo: 'bar' };
λ.emitter(thing);
thing.on('error', function (err) {
console.log(err);
});
thing.emit('error', 'foo');
<- 'foo'
```
[_Back to top_](#quick-links)
## `λ.curry(fn, ...arguments)`
Returns a function bound with some arguments and a `next` callback.
```js
λ.curry(fn, 1, 3, 5);
// <- function (next) { fn(1, 3, 5, next); }
```
[_Back to top_](#quick-links)
# Comparison with `async`
[`async`][1]|`λ`
---|---
Aimed at Noders|Tailored for browsers
Arrays for [some][5], collections for [others][6]|Collections for **everyone**!
`apply`|`curry`
`parallel`|`concurrent`
`parallelLimit`|`concurrent`
`mapSeries`|`map.series`
More _comprehensive_|More _focused_
`~29.6k (minified, uncompressed)`|`~2.7k (minified, uncompressed)`
`λ` isn't meant to be a replacement for `async`. It aims to provide a more focused library, and a bit more consistency.
[_Back to top_](#quick-links)
# Browser Support
[![Browser Support](https://ci.testling.com/bevacqua/contra.png)](https://ci.testling.com/bevacqua/contra)
If you need support for one of the legacy browsers listed below, you'll need `contra.shim.js`.
- IE < 10
- Safari < 6
- Opera < 16
```js
require('contra/shim');
var λ = require('contra');
```
```html
```
The shim currently clocks around `~1.2k` minified, uncompressed.
[_Back to top_](#quick-links)
# License
MIT
[_Back to top_](#quick-links)
[logo]: https://raw.github.com/bevacqua/contra/master/resources/contra.png
[1]: https://github.com/caolan/async
[2]: https://github.com/lodash/lodash
[3]: https://github.com/bevacqua/contra/tree/master/src/contra.js
[4]: https://github.com/bevacqua
[5]: https://github.com/caolan/async#maparr-iterator-callback
[6]: https://github.com/caolan/async#paralleltasks-callback