Home

Awesome

contra.png

Asynchronous flow control with a functional taste to it

λ aims to stay small and simple, while powerful. Inspired by async and lodash. 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

API

Flow Control

Functional

Uncategorized

Install

Install using npm or bower. Or get the source code and embed that in a <script> tag.

npm i contra --save
bower i contra --save

You can use it as a Common.JS module, or embed it directly in your HTML.

var λ = require('contra');
<script src='contra.js'></script>
<script>
var λ = contra;
</script>

<sub>The only reason contra isn't published as λ directly is to make it easier for you to type.</sub>

<sub>Back to top</sub>

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.

λ.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'
});

<sub>Back to top</sub>

λ.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.

λ.concurrent([
  function (cb) {
    setTimeout(function () {
      cb(null, 'boom');
    }, 1000);
  },
  function (cb) {
    cb(null, 'foo');
  }
], function (err, results) {
  console.log(results);
  // <- ['boom', 'foo']
});

Using objects

λ.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' }
});

<sub>Back to top</sub>

λ.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.

λ.series([
  function (next) {
    setTimeout(function () {
      next(null, 'boom');
    }, 1000);
  },
  function (next) {
    next(null, 'foo');
  }
], function (err, results) {
  console.log(results);
  // <- ['boom', 'foo']
});

Using objects

λ.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' }
});

<sub>Back to top</sub>

λ.each(items, cap?, iterator, done?)

Applies an iterator to each element in the collection concurrently.

λ.each({ thing: 900, another: 23 }, function (item, cb) {
  setTimeout(function () {
    console.log(item);
    cb();
  }, item);
});
// <- 23
// <- 900

<sub>Back to top</sub>

λ.each.series(items, iterator, done?)

Effectively an alias for λ.each(items, 1, iterator, done?).

<sub>Back to top</sub>

λ.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.

λ.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 }
});

<sub>Back to top</sub>

λ.map.series(items, iterator, done?)

Effectively an alias for λ.map(items, 1, iterator, done?).

<sub>Back to top</sub>

λ.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.

λ.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 }
});

<sub>Back to top</sub>

λ.filter.series(items, iterator, done?)

Effectively an alias for λ.filter(items, 1, iterator, done?).

<sub>Back to top</sub>

λ.queue(worker, cap=1)

Used to create a job queue.

Returns a queue you can push or unshift jobs to. You can pause and resume the queue by hand.

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!'

<sub>Back to top</sub>

λ.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.

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).

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.

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.

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.

var thing = { foo: 'bar' };

λ.emitter(thing);

thing.on('error', function (err) {
  console.log(err);
});

thing.emit('error', 'foo');
<- 'foo'

<sub>Back to top</sub>

λ.curry(fn, ...arguments)

Returns a function bound with some arguments and a next callback.

λ.curry(fn, 1, 3, 5);
// <- function (next) { fn(1, 3, 5, next); }

<sub>Back to top</sub>

Comparison with async

asyncλ
Aimed at NodersTailored for browsers
Arrays for some, collections for othersCollections for everyone!
applycurry
parallelconcurrent
parallelLimitconcurrent
mapSeriesmap.series
More comprehensiveMore 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.

<sub>Back to top</sub>

Browser Support

Browser Support

If you need support for one of the legacy browsers listed below, you'll need contra.shim.js.

require('contra/shim');
var λ = require('contra');
<script src='contra.shim.js'></script>
<script src='contra.js'></script>
<script>
var λ = contra;
</script>

The shim currently clocks around ~1.2k minified, uncompressed.

<sub>Back to top</sub>

License

MIT

<sub>Back to top</sub>