{"id":13509024,"url":"https://github.com/bevacqua/contra","last_synced_at":"2025-12-12T04:22:40.001Z","repository":{"id":13280558,"uuid":"15966223","full_name":"bevacqua/contra","owner":"bevacqua","description":":surfer: Asynchronous flow control with a functional taste to it","archived":false,"fork":false,"pushed_at":"2024-03-16T02:11:28.000Z","size":283,"stargazers_count":779,"open_issues_count":1,"forks_count":27,"subscribers_count":27,"default_branch":"master","last_synced_at":"2025-05-07T18:07:49.925Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://ponyfoo.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bevacqua.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-01-16T11:41:17.000Z","updated_at":"2025-03-23T14:42:16.000Z","dependencies_parsed_at":"2024-06-20T22:02:30.640Z","dependency_job_id":"4cbf5dbb-3e1b-447a-b5f7-8f96ac56b760","html_url":"https://github.com/bevacqua/contra","commit_stats":{"total_commits":255,"total_committers":5,"mean_commits":51.0,"dds":0.03529411764705881,"last_synced_commit":"661cc16335ea6cf91f16965bea4c8930c09b2f2a"},"previous_names":["bevacqua/a"],"tags_count":71,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bevacqua%2Fcontra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bevacqua%2Fcontra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bevacqua%2Fcontra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bevacqua%2Fcontra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bevacqua","download_url":"https://codeload.github.com/bevacqua/contra/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254198515,"owners_count":22030966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T02:01:01.917Z","updated_at":"2025-12-12T04:22:39.941Z","avatar_url":"https://github.com/bevacqua.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","others","Libraries"],"sub_categories":["[Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)"],"readme":"![contra.png][logo]\n\n\u003e Asynchronous flow control with a functional taste to it\n\n`λ` 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!\n\nFeature requests will be considered on a case-by-case basis.\n\n#### Quick Links\n\n- [CHANGELOG](CHANGELOG.md)\n- [Comparison with `async`](#comparison-with-async)\n- [Browser Support](#browser-support)\n- [License](#License)\n\n#### API\n\nFlow Control\n\n- [`λ.waterfall`](#%CE%BBwaterfalltasks-done)\n- [`λ.series`](#%CE%BBseriestasks-done)\n- [`λ.concurrent`](#%CE%BBconcurrenttasks-cap-done)\n\nFunctional\n\n- [`λ.each`](#%CE%BBeachitems-cap-iterator-done)\n- [`λ.each.series`](#%CE%BBeachseriesitems-iterator-done)\n- [`λ.map`](#%CE%BBmapitems-cap-iterator-done)\n- [`λ.map.series`](#%CE%BBmapseriesitems-iterator-done)\n- [`λ.filter`](#%CE%BBfilteritems-cap-iterator-done)\n- [`λ.filter.series`](#%CE%BBfilterseriesitems-iterator-done)\n\nUncategorized\n\n- [`λ.queue`](#%CE%BBqueueworker-cap1)\n- [`λ.emitter`](#%CE%BBemitterthing-options)\n- [`λ.curry`](#%CE%BBcurryfn-arguments)\n\n# Install\n\nInstall using `npm` or `bower`. Or get the [source code][3] and embed that in a `\u003cscript\u003e` tag.\n\n```shell\nnpm i contra --save\n```\n\n```shell\nbower i contra --save\n```\n\nYou can use it as a Common.JS module, or embed it directly in your HTML.\n\n```js\nvar λ = require('contra');\n```\n\n```html\n\u003cscript src='contra.js'\u003e\u003c/script\u003e\n\u003cscript\u003e\nvar λ = contra;\n\u003c/script\u003e\n```\n\n\u003csub\u003eThe only reason `contra` isn't published as `λ` directly is to make it easier for you to type.\u003c/sub\u003e\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n# API\n\nThese are the asynchronous flow control methods provided by `λ`.\n\n## `λ.waterfall(tasks, done?)`\n\nExecutes tasks in series. Each step receives the arguments from the previous step.\n\n- `tasks` Array of functions with the `(...results, next)` signature\n- `done` Optional function with the `(err, ...results)` signature\n\n```js\nλ.waterfall([\n  function (next) {\n    next(null, 'params for', 'next', 'step');\n  },\n  function (a, b, c, next) {\n    console.log(b);\n    // \u003c- 'next'\n    next(null, 'ok', 'done');\n  }\n], function (err, ok, result) {\n  console.log(result);\n  // \u003c- 'done'\n});\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.concurrent(tasks, cap?, done?)`\n\nExecutes 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.\n\n- `tasks` Collection of functions with the `(cb)` signature. Can be an array or an object\n- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)\n- `done` Optional function with the `(err, results)` signature\n\n```js\nλ.concurrent([\n  function (cb) {\n    setTimeout(function () {\n      cb(null, 'boom');\n    }, 1000);\n  },\n  function (cb) {\n    cb(null, 'foo');\n  }\n], function (err, results) {\n  console.log(results);\n  // \u003c- ['boom', 'foo']\n});\n```\n\nUsing objects\n\n```js\nλ.concurrent({\n  first: function (cb) {\n    setTimeout(function () {\n      cb(null, 'boom');\n    }, 1000);\n  },\n  second: function (cb) {\n    cb(null, 'foo');\n  }\n}, function (err, results) {\n  console.log(results);\n  // \u003c- { first: 'boom', second: 'foo' }\n});\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.series(tasks, done?)`\n\n**Effectively an alias for `λ.concurrent(tasks, 1, done?)`.**\n\nExecutes 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.\n\n- `tasks` Collection of functions with the `(next)` signature. Can be an array or an object\n- `done` Optional function with the `(err, results)` signature\n\n```js\nλ.series([\n  function (next) {\n    setTimeout(function () {\n      next(null, 'boom');\n    }, 1000);\n  },\n  function (next) {\n    next(null, 'foo');\n  }\n], function (err, results) {\n  console.log(results);\n  // \u003c- ['boom', 'foo']\n});\n```\n\nUsing objects\n\n```js\nλ.series({\n  first: function (next) {\n    setTimeout(function () {\n      next(null, 'boom');\n    }, 1000);\n  },\n  second: function (next) {\n    next(null, 'foo');\n  }\n}, function (err, results) {\n  console.log(results);\n  // \u003c- { first: 'boom', second: 'foo' }\n});\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.each(items, cap?, iterator, done?)`\n\nApplies an iterator to each element in the collection concurrently.\n\n- `items` Collection of items. Can be an array or an object\n- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)\n- `iterator(item, key?, cb)` Function to execute on each item\n  - `item` The current item\n  - `key` Optional, array/object key of the current item\n  - `cb` Needs to be called when processing for current item is done\n- `done` Optional function with the `(err)` signature\n\n```js\nλ.each({ thing: 900, another: 23 }, function (item, cb) {\n  setTimeout(function () {\n    console.log(item);\n    cb();\n  }, item);\n});\n// \u003c- 23\n// \u003c- 900\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.each.series(items, iterator, done?)`\n\nEffectively an alias for `λ.each(items, 1, iterator, done?)`.\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.map(items, cap?, iterator, done?)`\n\nApplies an iterator to each element in the collection concurrently. Produces an object with the transformation results. Task order is preserved in the results.\n\n- `items` Collection of items. Can be an array or an object\n- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)\n- `iterator(item, key?, cb)` Function to execute on each item\n  - `item` The current item\n  - `key` Optional, array/object key of the current item\n  - `cb` Needs to be called when processing for current item is done\n- `done` Optional function with the `(err, results)` signature\n\n```js\nλ.map({ thing: 900, another: 23 }, function (item, cb) {\n  setTimeout(function () {\n    cb(null, item * 2);\n  }, item);\n}, function (err, results) {\n  console.log(results);\n  \u003c- { thing: 1800, another: 46 }\n});\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.map.series(items, iterator, done?)`\n\nEffectively an alias for `λ.map(items, 1, iterator, done?)`.\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.filter(items, cap?, iterator, done?)`\n\nApplies an iterator to each element in the collection concurrently. Produces an object with the filtered results. Task order is preserved in results.\n\n- `items` Collection of items. Can be an array or an object\n- `cap` Optional concurrency level, used by the internal [queue](#%CE%BBqueueworker-cap1)\n- `iterator(item, key?, cb)` Function to execute on each item\n  - `item` The current item\n  - `key` Optional, array/object key of the current item\n  - `cb` Needs to be called when processing for current item is done\n    - `err` An optional error which will short-circuit the filtering process, calling `done`\n    - `keep` Truthy will keep the item. Falsy will remove it in the results\n- `done` Optional function with the `(err, results)` signature\n\n```js\nλ.filter({ thing: 900, another: 23, foo: 69 }, function (item, cb) {\n  setTimeout(function () {\n    cb(null, item % 23 === 0);\n  }, item);\n}, function (err, results) {\n  console.log(results);\n  \u003c- { another: 23, foo: 69 }\n});\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.filter.series(items, iterator, done?)`\n\nEffectively an alias for `λ.filter(items, 1, iterator, done?)`.\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.queue(worker, cap=1)`\n\nUsed to create a job queue.\n\n- `worker(job, done)` Function to process jobs in the queue\n  - `job` The current job\n  - `done` Needs to be called when processing for current job is done\n- `cap` Optional concurrency level, defaults to `1` (serial)\n\nReturns a queue you can `push` or `unshift` jobs to. You can pause and resume the queue by hand.\n\n- `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\n- `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\n- `pending` Property. Jobs that haven't started processing yet\n- `length` Short-hand for `pending.length`, only works if getters can be defined\n- `pause()` Stop processing jobs. Those already being processed will run to completion\n- `resume()` Start processing jobs again, after a `pause()`\n- `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`\n\n```js\nvar q = λ.queue(worker);\n\nfunction worker (job, done) {\n  console.log(job);\n  done(null);\n}\n\nq.push('job', function () {\n  console.log('this job is done!');\n});\n\nq.push(['some', 'more'], function () {\n  console.log('one of these jobs is done!');\n});\n\nq.on('drain', function () {\n  console.log('all done!');\n  // if you enqueue more tasks now, then drain\n  // will fire again when pending.length reaches 0\n});\n\n// \u003c- 'this job is done!'\n// \u003c- 'one of these jobs is done!'\n// \u003c- 'one of these jobs is done!'\n// \u003c- 'all done!'\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.emitter(thing={}, options={})`\n\nAugments `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.\n\n- `thing` Optional. Writable JavaScript object\n- `emit(type, ...arguments)` Emits an event of type `type`, passing any `...arguments`\n- `emitterSnapshot(type)` Returns a function you can call, passing any `...arguments`\n- `on(type, fn)` Registers an event listener `fn` for `type` events\n- `once(type, fn)` Same as `on`, but the listener is discarded after one callback\n- `off(type, fn)` Unregisters an event listener `fn` from `type` events\n- `off(type)` Unregisters all event listeners from `type` events\n- `off()` Unregisters all event listeners\n\nThe `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)_.\n\n```js\nvar thing = λ.emitter();\n\nthing.on('foo', foo);\nthing.on('bar', bar);\nthing.on('destroy', function () {\n  thing.on('create', reinitialize);\n});\n\nvar destroy = thing.emitterSnapshot('destroy');\nthing.off();\ndestroy();\n```\n\nThe emitter can be configured with the following options, too.\n\n- `async` Debounce listeners asynchronously. By default they're executed in sequence.\n- `throws` Throw an exception if an `error` event is emitted and no listeners are defined. Defaults to `true`.\n\n```js\nvar thing = λ.emitter(); // also, λ.emitter({ foo: 'bar' })\n\nthing.once('something', function (level) {\n  console.log('something FIRST TROLL');\n});\n\nthing.on('something', function (level) {\n  console.log('something level ' + level);\n});\n\nthing.emit('something', 4);\nthing.emit('something', 5);\n// \u003c- 'something FIRST TROLL'\n// \u003c- 'something level 4'\n// \u003c- 'something level 5'\n```\n\nReturns `thing`.\n\nEvents 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.\n\n```js\nvar thing = { foo: 'bar' };\n\nλ.emitter(thing);\n\nthing.emit('error', 'foo');\n\u003c- throws 'foo'\n```\n\nIf an `'error'` listener is registered, then it'll work just like any other event type.\n\n```js\nvar thing = { foo: 'bar' };\n\nλ.emitter(thing);\n\nthing.on('error', function (err) {\n  console.log(err);\n});\n\nthing.emit('error', 'foo');\n\u003c- 'foo'\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n## `λ.curry(fn, ...arguments)`\n\nReturns a function bound with some arguments and a `next` callback.\n\n```js\nλ.curry(fn, 1, 3, 5);\n// \u003c- function (next) { fn(1, 3, 5, next); }\n```\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n# Comparison with `async`\n\n[`async`][1]|`λ`\n---|---\nAimed at Noders|Tailored for browsers\nArrays for [some][5], collections for [others][6]|Collections for **everyone**!\n`apply`|`curry`\n`parallel`|`concurrent`\n`parallelLimit`|`concurrent`\n`mapSeries`|`map.series`\nMore _comprehensive_|More _focused_\n`~29.6k (minified, uncompressed)`|`~2.7k (minified, uncompressed)`\n\n`λ` isn't meant to be a replacement for `async`. It aims to provide a more focused library, and a bit more consistency.\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n# Browser Support\n\n[![Browser Support](https://ci.testling.com/bevacqua/contra.png)](https://ci.testling.com/bevacqua/contra)\n\nIf you need support for one of the legacy browsers listed below, you'll need `contra.shim.js`.\n\n- IE \u003c 10\n- Safari \u003c 6\n- Opera \u003c 16\n\n```js\nrequire('contra/shim');\nvar λ = require('contra');\n```\n\n```html\n\u003cscript src='contra.shim.js'\u003e\u003c/script\u003e\n\u003cscript src='contra.js'\u003e\u003c/script\u003e\n\u003cscript\u003e\nvar λ = contra;\n\u003c/script\u003e\n```\n\nThe shim currently clocks around `~1.2k` minified, uncompressed.\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n# License\n\nMIT\n\n\u003csub\u003e[_Back to top_](#quick-links)\u003c/sub\u003e\n\n  [logo]: https://raw.github.com/bevacqua/contra/master/resources/contra.png\n  [1]: https://github.com/caolan/async\n  [2]: https://github.com/lodash/lodash\n  [3]: https://github.com/bevacqua/contra/tree/master/src/contra.js\n  [4]: https://github.com/bevacqua\n  [5]: https://github.com/caolan/async#maparr-iterator-callback\n  [6]: https://github.com/caolan/async#paralleltasks-callback\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbevacqua%2Fcontra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbevacqua%2Fcontra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbevacqua%2Fcontra/lists"}