{"id":13757709,"url":"https://github.com/hash-bang/async-chainable","last_synced_at":"2025-10-25T08:48:42.893Z","repository":{"id":25259239,"uuid":"28684383","full_name":"hash-bang/async-chainable","owner":"hash-bang","description":"An extension to Async adding better handling of mixed Series / Parallel tasks via object chaining","archived":false,"fork":false,"pushed_at":"2023-08-15T00:39:40.000Z","size":247,"stargazers_count":26,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-02-13T11:53:24.662Z","etag":null,"topics":["async","async-chainable","asynchronous","callback","hooks","javascript","parallel-tasks"],"latest_commit_sha":null,"homepage":null,"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/hash-bang.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2015-01-01T10:06:13.000Z","updated_at":"2024-02-13T11:53:26.649Z","dependencies_parsed_at":"2024-02-13T11:53:26.401Z","dependency_job_id":"704853a8-28ac-49ed-9c20-7612c3109b50","html_url":"https://github.com/hash-bang/async-chainable","commit_stats":{"total_commits":195,"total_committers":2,"mean_commits":97.5,"dds":0.02564102564102566,"last_synced_commit":"9f2ef431598472ed3ac8d8a518b3108ef138c0e8"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2Fasync-chainable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2Fasync-chainable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2Fasync-chainable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hash-bang%2Fasync-chainable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hash-bang","download_url":"https://codeload.github.com/hash-bang/async-chainable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247934160,"owners_count":21020714,"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":["async","async-chainable","asynchronous","callback","hooks","javascript","parallel-tasks"],"created_at":"2024-08-03T12:00:46.226Z","updated_at":"2025-10-25T08:48:37.843Z","avatar_url":"https://github.com/hash-bang.png","language":"JavaScript","readme":"async-chainable\n===============\nFlow control for NodeJS applications.\n\nThis builds on the foundations of the [Async](https://www.npmjs.com/package/async) library while adding better handling of mixed Series / Parallel tasks via object chaining.\n\n```javascript\nvar asyncChainable = require('async-chainable');\n\nasyncChainable() // \u003c- Note '()' as this module is not stateless\n\t.parallel([fooFunc, barFunc, bazFunc]) // Do these operations in parallel THEN\n\t.series([fooFunc, barFunc, bazFunc]) // Do these in series (NOTE: these only run when the above has resolved)\n\t.end(console.log)\n\n\nasyncChainable()\n\t.limit(2) // Allow only 2 defer items to run at once from this point on\n\t.defer('foo', fooFunc) // Run this now and continue on...\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await('foo', 'bar') // Wait for 'foo' and 'bar' parallel functions to finish but we don't care about 'baz' yet\n\t.then(function() { console.log(this.foo, this.bar) }) // Output: null, {foo: 'foo value', bar: 'bar value'}\n\t.await() // Wait for everything else\n\t.end(function() { console.log(this) }) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n\n\nasyncChainable()\n\t.parallel('foo', fooFunc)\n\t.prereq('foo', 'bar', barFunc) // Only let this function run when 'foo' has completed\n\t.prereq(['foo', 'bar'], 'baz', bazFunc) // Only let this function run when both 'foo' and 'bar' have completed\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n\n\nasyncChainable()\n\t.forEach([\n\t\t'What do we want?',\n\t\t'Race conditions!',\n\t\t'When do we want them?',\n\t\t'Whenever!',\n\t], function(next, item) {\n\t\t// Prints the above array items to the console in parallel (i.e. whichever resolve first - no gurenteed order)\n\t\tconsole.log(item);\n\t\tnext();\n\t})\n\t.end();\n\n\n\n// Or use contexts (i.e. `this`) - see Contexts section for more information\nasyncChainable()\n\t.parallel({\n\t\tfoo: fooFunc,\n\t\tbar: barFunc,\n\t\tbaz: bazFunc\n\t})\n\t.then(function(next) {\n\t\tconsole.log(this); // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value', /* META INFO */}\n\t\tconsole.log('Foo =', this.foo); // Output: 'Foo = ', 'foo value'\n\t\tnext();\n\t})\n\t.end();\n\n\n// Call in stages without chaining\nvar tasks = asyncChainable();\n\ntasks.defer('foo', fooFunc);\n\ntasks.defer('bar', barFunc);\n\nif (/* some internal logic */) {\n\ttasks.defer('bar', 'baz', bazFunc); // Only start 'baz' when 'bar' has completed\n}\n\ntasks.end(); // Kick everything off\n\n\n\n// Specify prerequisites and let async-chainable figure everything out automatically\n// This style replaces async.auto() which is a bit ugly\n\nasyncChainable()\n\n\t// Task 'foo' relies on 'quz'\n\t.defer('quz', 'foo', function(next) { next(null, 'fooValue') })\n\n\t// Task 'bar' relies on 'baz' and 'foo'\n\t.defer(['baz', 'foo'], 'bar', function(next) { next(null, 'barValue') })\n\n\t// Task 'bar' doesnt need any pre-requisites\n\t.defer('baz', function(next) { next(null, 'bazValue') })\n\n\t// Task 'quz' relies on 'baz'\n\t.defer('baz', 'quz', function(next) { next(null, 'quzValue') })\n\t\n\t// Wait for everything to finish\n\t.await()\n\t.end();\n\n\n// Use async-chainable within ExpressJS\n// This example provides an `/order/123` style URL where the order is fetched and returned as a JSON object\n\napp.get('/order/:id', function(req, res) {\n\tasyncChainable()\n\t\t.then(function(next) { // Sanity checks\n\t\t\tif (!req.params.id) return next('No ID specified');\n\t\t\tnext();\n\t\t})\n\t\t.then('order', function(next) { // Fetch order into this.order\n\t\t\tOrders.findOne({_id: req.params.id}, next);\n\t\t})\n\t\t.then(function(next) {\n\t\t\t// Do something complicated\n\t\t\tsetTimeout(next, 1000);\n\t\t})\n\t\t.end(function(err) {\n\t\t\tif (err) return res.status(400).send(err);\n\t\t\tres.send(this.order);\n\t\t});\n});\n```\n\nProject Goals\n=============\nThis project has the following goals:\n\n* Be semi-compatible with the [Async](https://www.npmjs.com/package/async) library so existing applications are portable over time\n* Provide a readable and dependable model for asynchronous tasks\n* Have a 'sane' ([YMMV](http://tvtropes.org/pmwiki/pmwiki.php/Main/YMMV)) syntax that will fit most use cases\n* Have an extendible plugin system to allow [additional components](#plugins) to be easily brought into the project\n* Work with any async paradigm - callbacks, async functions, promises etc.\n\n\nPlugins\n=======\nThere are a number of async-chainable plugins available which can extend the default functionality of the module:\n\n* [async-chainable-cartesian](https://github.com/hash-bang/async-chainable-cartesian) - Compare large numbers of records against one another\n* [async-chainable-compat](https://github.com/hash-bang/async-chainable-compat) - [Async](https://www.npmjs.com/package/async) compatibility layer\n* [async-chainable-exec](https://github.com/hash-bang/async-chainable-exec) - External program support\n* [async-chainable-flush](https://github.com/hash-bang/async-chainable-flush) - Wait for streams to flush before continuing\n* [async-chainable-log](https://github.com/hash-bang/async-chainable-log) - Simple logging extension\n* [async-chainable-nightmare](https://github.com/hash-bang/async-chainable-nighmare) - Wrapper around [Nightmare](https://github.com/segmentio/nightmare) to automate a scriptable browser\n* [async-chainable-progress](https://github.com/hash-bang/async-chainable-progress) - Adds progress bars, spinners, tick lists and other widgets for long running task chains\n\n\nFAQ\n===\nSome frequently asked questions:\n\n* **Why not just use Async?** - Async is an excellent library and suitable for 90% of tasks out there but it quickly becomes unmanageable when dealing with complex nests such as a mix of series and parallel tasks.\n\n* **Why was this developed?** - Some research I was doing involved the nesting of ridiculously complex parallel and series based tasks and Async was becoming more of a hindrance than a helper.\n\n* **What alternatives are there to this library?** - The only ones I've found that come close are [node-seq](https://github.com/substack/node-seq) and [queue-async](https://www.npmjs.com/package/queue-async) but both of them do not provide the functionality listed here\n\n* **Is this the module I should use for Async JavaScript?** - If you're doing simple parallel or series based tasks use [Async](https://www.npmjs.com/package/async), if you're doing complex nested operations you might want to take a look at this one\n\n* **Whats license do you use?** - We use the [MIT license](LICENSE), please credit the original library and authors if you wish to fork or share\n\n* **Who wrote this / who do I blame?** - [Matt Carter](https://github.com/hash-bang) and [David Porter](https://github.com/DesertLynx)\n\n\nMore complex examples\n=====================\n\n```javascript\nvar asyncChainable = require('async-chainable');\n\n// Simple nesting of series and parallel operations\nasyncChainable()\n\t// The following 3 functions execute in series\n\t.series([\n\t\tfunction(next) { setTimeout(function() { console.log('Series 1'); next(); }, 100); },\n\t\tfunction(next) { setTimeout(function() { console.log('Series 2'); next(); }, 200); },\n\t\tfunction(next) { setTimeout(function() { console.log('Series 3'); next(); }, 300); },\n\t])\n\n\t// ...then we run this...\n\t.then(function(next) {\n\t\tconsole.log('Finished step 1');\n\t})\n\n\t// ...then the next three run in parallel\n\t.parallel([\n\t\tfunction(next) { setTimeout(function() { console.log('Parallel 1'); next(); }, 300); },\n\t\tfunction(next) { setTimeout(function() { console.log('Parallel 2'); next(); }, 200); },\n\t\tfunction(next) { setTimeout(function() { console.log('Parallel 3'); next(); }, 100); },\n\t])\n\t.end(function(next) {\n\t\tconsole.log('Finished simple example');\n\t});\n\n\n// Parameters can be passed by using named functions\n\nasyncChainable()\n\t.series({ // Since this is node we can KINDA rely on it storing the hash in the right order, don't splice or alter the hash past declaring it though or this functionality will break. Alternatively use the below syntax\n\t\tfoo: function(next) {\n\t\t\tsetTimeout(function() { console.log('Series 2-1'); next(null, 'foo result'); }, 100);\n\t\t},\n\t\tbar: function(next, results) {\n\t\t\t// We can access results from any function\n\t\t\tsetTimeout(function() { console.log('Series 2-2'); next(null, 'bar result'); }, 100);\n\t\t},\n\t\tbaz: function(next) {\n\t\t\tsetTimeout(function() { console.log('Series 2-3'); next(null, 'baz result'); }, 100);\n\t\t},\n\t})\n\t.parallel({ // See above comment about Node storing hashes in the right accessible order\n\t\tfooParallel: function(next) {\n\t\t\tsetTimeout(function() { console.log('Series 2-1'); next(null, 'foo parallel result'); }, 100);\n\t\t},\n\t\tbarParallel: function(next) {\n\t\t\tsetTimeout(function() { console.log('Series 2-2'); next(null, 'bar parallel result'); }, 100);\n\t\t},\n\t\tbazParallel: function(next) {\n\t\t\tsetTimeout(function() { console.log('Series 2-3'); next(null, 'baz parallel result'); }, 100);\n\t\t},\n\t})\n\t.then(function(next, results) {\n\t\t// We also get all the results at the end\n\t\tconsole.log(\"Results\", results); // results = {foo: 'foo result', bar: 'bar result'... 'fooParallel': 'foo parallel result'...}\n\t})\n\t.reset() // Or we can clear out the results manually\n\t.end(function(next, results) {\n\t\tconsole.log('Results should be blank', results);\n\t};\n\n\n// In the below examples we assume fooFunc, barFunc, bazFunc and quzFunc functions look something like this:\nfooFunc = barFunc = bazFunc = quzFunc = function(next) {\n\tsetTimeout(function() {\n\t\tnext(null, arguments.callee.toString().substr(0, 3) + ' value'); // Continue on with 'foo value', 'bar value' etc.\n\t}, Math.random() * 1000); // After a random wait of up to a second\n};\n\n// Alternative syntaxes\n// All of the below are syntactically the same and will output null, 'foo', 'bar', 'baz (first value is error)\n// In each we assume 'series' is the rule but 'parallel' could be substituted if we don't care about sequence\n// In each case we have named the functions (e.g. 'foo') but we could just omit this and have a function if we don't care about the result\n\n// Hash style syntax\n// NOTE: This relies on the hash not changing order of the keys which is a bit of an issue if you're adding / removing from it out of sequence\nasyncChainable()\n\t.series({foo: fooFunc, bar: barFunc, baz: bazFunc})\n\t.end(console.log);\n\n// Named function style syntax - series only\nasyncChainable()\n\t.then('foo', fooFunc)\n\t.then('bar', barFunc)\n\t.then('baz', bazFunc)\n\t.end(console.log);\n\n// Named function style syntax - parallel only\nasyncChainable()\n\t.defer('foo', fooFunc) // Start this function immediately and continue on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await() // Resolve all deferred functions before continuing\n\t.end(console.log);\n\n// Named function style syntax - parallel only\n// This works the same as above where if parallel() is NOT passed a hash or array it will act as if called with defer()\nasyncChainable()\n\t.parallel('foo', fooFunc) // Start this function immediately and continue on\n\t.parallel('bar', barFunc)\n\t.parallel('baz', bazFunc)\n\t.await() // Resolve all deferred functions before continuing\n\t.end(console.log);\n\nasyncChainable()\n\t.limit(2) // Allow only 2 defer operations to run at once\n\t.defer('foo', fooFunc)\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await('foo', 'bar') // Wait for 'foo' and 'bar' parallel functions to finish but we don't care about 'baz' yet\n\t.then(console.log) // Output: null, {foo: 'foo value', bar: 'bar value'}\n\t.await() // Wait for everything else\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n\n\n\n// Classic asyncChainable functionality is still available\n\nasyncChainable()\n\t.waterfall([\n\t\tfunction(next) { next(null, 'foo') };\n\t\tfunction(next, fooResult) { console.log(fooResult); next(); } // Output = 'foo'\n\t]);\n```\n\nAPI\n===\n\n.await()\n--------\nWait for one or more fired defer functions to complete before containing down the asyncChainable chain.\n\n\tawait() // Wait for all defered functions to finish\n\tawait(string) // Wait for at least the named defer to finish\n\tawait(string,...) // Wait for the specified named defers to finish\n\tawait(array) // Wait for the specified named defers to finish\n\n\nSome examples:\n\n```javascript\n// Wait for everything\nasyncChainable()\n\t.defer('foo', fooFunc) // Execute fooFunc() and immediately move on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await() // Wait for all defers to finish\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n\n\n// Wait for certain defers\nasyncChainable()\n\t.defer('foo', fooFunc) // Execute fooFunc() and immediately move on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await('foo', 'bar') // Wait for 'foo' and 'bar' parallel functions to finish but we dont care about 'baz' yet\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value'}\n\n\n// Wait for certain defers - array syntax\nasyncChainable()\n\t.defer('foo', fooFunc) // Execute fooFunc() and immediately move on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await(['foo', 'bar']) // Wait for 'foo' and 'bar' parallel functions to finish but we dont care about 'baz' yet\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value'}\n```\n\n\n.context()\n----------\nSet the context used by async-chainable during subsequent function calls.\nIn effect this sets what `this` is for each call.\nOmitting an argument or supplying a 'falsy' value will instruct async-chainable to use its own default context object.\n\n```javascript\nasyncChainable()\n\t.then({foo: fooFunc}) // `this` is async-chainables own context object\n\t.context({hello: 'world'})\n\t.then({bar: barFunc}) // `this` is now {hello: 'world'}\n\t.context()\n\t.then({baz: bazFunc}) // `this` is now async-chainables own context object again\n\t.end(this) // Output: null, {foo: 'foo value', bar: 'bar value', quz: 'quz value'}\n```\n\nNote that even if the context is switched async-chainable still stores any named values in its own context for later retrieval (in the above example this is `barFunc()` returning a value even though the context has been changed to a custom object).\n\nSee the [Context Section](#context) for further details on what the async-chainable context object contains.\n\n\n.defer()\n--------\nExecute a function and continue down the asyncChainable chain.\n\n\tdefer(function)\n\tdefer(string, function) // Named function (`this.name` gets set to whatever gets passed to `next()`)\n\tdefer(string, string, function) // Named function (name is second arg) with prereq (first arg)\n\tdefer(array, function) // Run an anonymous function with the specified pre-reqs\n\tdefer(array, string, function) // Named function (name is second arg) with prereq array (first arg)\n\tdefer(string, array, function) // Name function (name is first, prereqs second) this is a varient of the above which matches the `gulp.task(id, prereq)` syntax\n\tdefer(array)\n\tdefer(object) // Named function object (each object key gets assigned to this with the value passed to `next()`)\n\tdefer(collection) // See 'object' definition\n\tdefer(null) // Gets skipped automatically\n\n\nUse `await()` to gather the parallel functions.\nThis can be considered the parallel process twin to `series()` / `then()`.\n\n```javascript\nasyncChainable()\n\t.defer('foo', fooFunc) // Execute fooFunc() and immediately move on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await('foo', 'bar') // Wait for 'foo' and 'bar' parallel functions to finish but we dont care about 'baz' yet\n\t.then(console.log) // Output: null, {foo: 'foo value', bar: 'bar value'}\n\t.await() // Wait for everything else\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n```\n\n\n**NOTE**: All defers take their 'next' handler as the first argument. All subsequent arguments are the resolved value of the prerequisites. In the above example `barFunc` would be called as `barFunc(next, resultOfFoo)` and `bazFunc` would be called as `bazFunc(next, resultOfBaz)`.\n\n\n.end()\n------\nThe final stage in the chain, `.end()` must be called to execute the queue of actions.\n\n\tend() // Run the chain but don't do anything when completed\n\tend(function) // Final function to execute as `function(err)`\n\tend(string, function) // Extract the context value specified by string and provide it to the callback \n\nNOTE: The form `end(string, function)` is used  is the internal equivalent to `end(err =\u003e callback(err, this[string]))` - i.e. the context value is extracted for you.\n\n\nWhile similar to `series()` / `then()` this function will always be executed *last* and be given the error if any occurred in the form `function(err)`.\n\n```javascript\nasyncChainable()\n\t.then('foo', fooFunc) // Execute and wait for fooFunc() to complete\n\t.then('bar', barFunc) // Likewise barFunc()\n\t.then('baz', bazFunc) // Likewise bazFunc()\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n```\n\nIn the above if fooFunc, barFunc or bazFunc call next with a first parameter that is true execution will stop and continue on passing the error to `end()`:\n\n```javascript\nasyncChainable()\n\t.then('foo', fooFunc) // Assuming fooFunc calls next('This is an error')\n\t.then('bar', barFunc) // Gets skipped as we have an error\n\t.then('baz', bazFunc) // Gets skipped as we have an error\n\t.end(console.log) // Output: 'This is an error'\n```\n\nIf an error is caused in the middle of execution the result object is still available:\n\n```javascript\nasyncChainable()\n\t.then('foo', fooFunc) // Assuming this calls `next()` with next(null, 'foo value')\n\t.then('bar', barFunc) // Assuming this calls next('Error in bar')\n\t.then('baz', bazFunc) // Gets skipped as we have an error\n\t.end(console.log) // Output: 'Error in bar', {foo: 'foo value'}\n```\n\n\n.fire()\n-------\nTrigger a hook. This function will run a callback on completion whether or not any hooks executed.\n\n\tfire(string, function) // Fire a hook and run the callback on completion\n\tthis.fire(...) // Same as above invocations but accessible within a chain\n\n\n```javascript\nasyncChainable()\n\t.forEach(['foo', 'bar', 'baz'], function(next, item, key) { console.log(item) }) // Output: foo, bar and baz in whichever they evaluate\n\t.hook('hello', function(next) { console.log('Hello world!'); next() })\n\t.then(function(next) {\n\t\t// Trigger a hook then continue on\n\t\tthis.fire('hello, next);\n\t})\n\t.end();\n```\n\n\n.forEach()\n----------\nThe `forEach()` function is a slight variation on the `parallel()` function but with some additional behaviour.\n\n\n\tforEach(fromNumber, toNumber, function) // Call function toNumber-fromNumber times\n\tforEach(toNumber, function) // Call function toNumber times (same as `forEach(1, NUMBER, function)`)\n\tforEach(array, function) // Run each item in the array though `function(next, value)`\n\tforEach(object, function) // Run each item in the object though `function(next, value, key)`\n\tforEach(collection,function) // see 'array, function' definition (collections are just treated like an array of objects with 'forEach')\n\tforEach(string, function) // Lookup `this[string]` then process according to its type (see above type styles) - This is used for late binding\n\tforEach(string, array, function) // Perform a map operation on the array setting the `this[string]` to the ordered result return\n\tforEach(string, object, function) // Perform a map operation on an object array setting the `this[string]` to the result return\n\tforEach(string, string, function) // Lookup the first string and process it as a map operation according to its type (see above 2 examples) - This is used for late binding\n\tforEach(null) // Gets skipped automatically (also empty arrays, objects)\n\n\nIt can be given an array, object or collection as the first argument and a function as the second. All items in the array will be iterated over *in parallel* and passed to the function which is expected to execute a next condition returning an error if the forEach iteration should stop.\n\n```javascript\nasyncChainable()\n\t.forEach(['foo', 'bar', 'baz'], function(next, item, key) { console.log(item) }) // Output: foo, bar and baz in whichever they evaluate\n\t.end();\n```\n\nIn the above example the simple array is passed to the function with each payload item as a parameter and the iteration key (an offset if its an array or collection, a key if its an object).\n\n`forEach()` has one additional piece of behaviour where if the first argument is a string the context will be examined for a value to iterate over. The string can be a simple key to use within the passed object or a deeply nested path using dotted notation (e.g. `key1.key2.key3`).\n\n```javascript\nasyncChainable()\n\t.set({\n\t\titems: ['foo', 'bar', 'baz'],\n\t})\n\t.forEach('items', function(next, item, key) { console.log(item); next() }) // Output: foo, bar and baz in whichever order they evaluate\n\t.end();\n```\n\nThis allows *late binding* of variables who's content will only be examined when the chain item is executed.\n\n\n.getPath()\n----------\nGetPath is the utility function used by `forEach()` to lookup deeply nested objects or arrays to iterate over.\nIt is functionally similar to the Lodash `get()` function.\n\n\n.hook()\n-------\nAttach a callback hook to a named trigger. These callbacks can all fire errors themselves and can fire out of sequence, unlike normal chains.\nHooks can be defined multiple times - if multiple callbacks are registered they are fired in allocation order in *series*. If any hook raises an error the chain is terminated as though a callback raised an error.\nDefined hooks can be `start`, `end`, `timeout` as well as any user-defined hooks.\nHooks can also be registered within a callback via `this.hook(hook, callback)` unless context is reset.\nHooks can also have an optional id and an array of prerequisites\n\n\thook(string, function) // Register a callback against a hook\n\thook(array, function) // Register a callback against a number of hooks, if any fire the callback is called\n\thook(string, string, function) // Register a named hook\n\thook(string, array, function) // Register a hook with prerequisites\n\thook(string, string, array, function) // Register a named hook with an array of prerequisite hooks\n\tthis.hook(...) // Same as above invocations but accessible within a chain\n\n\n```javascript\nasyncChainable()\n\t.forEach(['foo', 'bar', 'baz'], function(next, item, key) { console.log(item) }) // Output: foo, bar and baz in whichever they evaluate\n\t.hook('start', function(next) { console.log('Start!'); next()  })\n\t.hook('end', function(next) { console.log('End!'); next()  })\n\t.hook(['start', 'end'], function(next) { console.log('Start OR End!'); next()  })\n\t.end();\n```\n\n\n.limit()\n--------\nRestrict the number of defer operations that can run at any one time.\n\n\tlimit() // Allow unlimited parallel / defer functions to execute at once after this chain item\n\tlimit(Number) // Restrict the number of parallel / defer functions after this chain item\n\n\nThis function can be used in the pipeline as many times as needed to change the limit as we work down the execution chain.\n\n```javascript\nasyncChainable()\n\t.limit(2) // Allow only 2 defer operations to run at once from this point onward\n\t.defer(fooFunc)\n\t.defer(barFunc)\n\t.defer(bazFunc)\n\t.defer(quzFunc)\n\t.await()\n\t.limit(3) // Allow 3 defer operations to run at once from this point onward\n\t.defer(fooFunc)\n\t.defer(barFunc)\n\t.defer(bazFunc)\n\t.defer(quzFunc)\n\t.await()\n\t.limit() // Allow unlimited operations to run at once from this point onward (0 / false / null is also permissable as 'unlimited')\n\t.defer(fooFunc)\n\t.defer(barFunc)\n\t.defer(bazFunc)\n\t.defer(quzFunc)\n\t.await()\n\t.end(console.log)\n```\n\n\n.map()\n------\nThe `map()` function is really just an alias for `forEach()` aimed specifically at the following functions:\n\n\tmap(string, array, function) // Perform a map operation on the array setting the `this[string]` to the ordered result return\n\tmap(string, object, function) // Perform a map operation on an object array setting the `this[string]` to the result return\n\tmap(string, string, function) // Lookup the first string and process it as a map operation according to its type (see above 2 examples) - This is used for late binding\n\n**NOTES:**\n\n* Each function is expected to return a result which is used to compose the new object / array.\n* The object return type can also specify an alternate key as the third parameter to the callback (i.e. `callback(error, valueReturn, keyReturn)`). If unspecified the original key is used, if specified it is used instead of the original.\n* Late binding maps can accept the same input and output key name in order to overwrite the original.\n\nSee the [test files](./test/map.js) for more examples.\n\n\n.promise()\n----------\nAlternative to `end()` which returns a JS standard promise instead of using the `.end(callback)` system.\n\n\tpromise() // Return a promise which will resolve with no value\n\tpromise(string) // Return a promise which will return with the extracted context value\n\tpromise(function) // Return a promise but also run a callback\n\tpromise(string, function) // Extract the context value specified by string and provide it to the callback \n\n\n```javascript\nasyncChainable()\n\t.then(doSomethingOne)\n\t.then(doSomethingTwo)\n\t.then(doSomethingThree)\n\t.promise()\n\t.then(function() { // Everything went well })\n\t.catch(function(err) { // Something went wrong })\n```\n\n\n.race()\n-------\nRun multiple functions setting the named key to the first function to return with a non-null, non-undefined value.\nIf an error is thrown *before or after* the result is achived it will be returned instead.\n\n```javascript\nasyncChainable()\n\t.race('myKey', [\n\t\tfooFunc,\n\t\tbarFunc,\n\t\tbazFunc,\n\t])\n\t.end(function(err) {\n\t\tconsole.log('myKey =', this.myKey);\n\t});\n```\n\n\n.reset()\n---------\nClear the result buffer, releasing all results held in memory.\n\n```javascript\nasyncChainable()\n\t.defer('foo', fooFunc) // Execute fooFunc() and immediately move on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await('foo', 'bar') // Wait for 'foo' and 'bar' parallel functions to finish but we dont care about 'baz' yet\n\t.then(console.log) // Output: null, {foo: 'foo value', bar: 'bar value'}\n\t.reset()\n\t.defer('quz', quzFunc)\n\t.end(console.log) // Output: null, {quz: 'quz value'}\n```\n\n\n.run()\n------\nInternal function to resolve a function, async function, promise etc. etc. and then run a callback.\n\n\trun([context], fn, cb, [args])\n\n\n.runArray()\n------\nInternal callback resolver. Run is used to execute an array of callbacks then run a final callback. This function is NOT chainable, will execute immediately and is documented here as it is useful when writing plugins.\n\n\trunArray(array, limit, callback)\n\n\n.runWhile()\n------\nInternal callback resolver until a function returns falsy. This function is NOT chainable, will execute immediately and is documented here as it is useful when writing plugins.\nUnlike `runArray()` this function does not require a precomputed array of items to iterate over which makes it a kind of generator function useful for potencially large data set iterations.\n\n\trunWhile(function(next, index) {}, limit, callback)\n\n\n.series() / .parallel()\n-----------------------\nExecute an array or object of functions either in series or parallel.\n\n\tseries(function)\n\tseries(string, function) // Named function (`this.name` gets set to whatever gets passed to `next()`)\n\tseries(array)\n\tseries(object) // Named function object (each object key gets assigned to this with the value passed to `next()`)\n\tseries(collection) // See 'object' definition\n\tseries(array, function) // Backwards compatibility with `async.series`\n\tseries(object, function) // Backwards compatibility with `async.series`\n\n\tparallel(function)\n\tparallel(string, function) // Named function (`this.name` gets set to whatever gets passed to `next()`)\n\tparallel(array)\n\tparallel(object) // Named function object (each object key gets assigned to this with the value passed to `next()`)\n\tparallel(collection) // See 'object' definition\n\tparallel(array, function) // Backwards compatibility with `parallel.series`\n\tparallel(object, function) // Backwards compatibility with `parallel.series`\n\n\nSome examples:\n\n```javascript\nasyncChainable()\n\t.parallel(Array) // Execute all items in the array in parallel\n\t.parallel(Object) // Execute all items in the object in parallel storing any return against the object key\n\t.parallel(Collection) // i.e. an array of objects - this is to work around JS not maintaining hash key orders\n\t.parallel(String, function) // Execute function now and await output, then store in object against key specified by String\n\t.parallel(function) // Exactly the same functionality as `defer()`\n\t.end()\n\n\nasyncChainable()\n\t.series(Array) // Execute all items in the array in parallel\n\t.series(Object) // Execute all items in the object in series storing any return against the object key\n\t.series(Collection) // i.e. an array of objects - this is to work around JS not maintaining hash key orders\n\t.series(String, function) // Execute function now and await output, then store in object against key specified by String\n\t.series(function) // Exactly the same functionality as `then()`\n\t.end()\n```\n\n\n.set()\n------\nSet is a helper function to quickly allocate the value of a context item as we move down the chain.\n\n\tset(string, mixed) // Set the single item in `this` specified the first string to the value of the second arg\n\tset(object) // Merge the object into `this` to quickly set a number of values\n\tset(function) // Alias for `series(function)`\n\tset(string, function) // Alias for `series(string, function)`\n\n\nIt can be used as a named single item key/value or as a setter object.\n\n```javascript\nasyncChainable()\n\t.set('foo', 'foo value')\n\t.then(function(next) { console.log(this.foo); next() }) // this.foo is now 'foo value'\n\t.set({bar: 'bar value'}) \n\t.then(function(next) { console.log(this.foo); next() }) // this.bar is now 'bar value' (as well as .foo being also set)\n\t.set(baz, function(next) { next(null, 'baz value') }) // this.baz is now 'baz value' (this is actually just an alias for .series())\n\t.then(function(next) { console.log(this.foo); next() }) // this.baz is now 'baz value' (as well as .foo, .bar being also set)\n\t.end()\n```\n\n\n.timeout()\n----------\nSet a delay and/or a callback to run if the Async chain goes over a specified timeout.\n\n\ttimeout(number) // Set the timeout delay\n\ttimeout(number, function) // Set the timeout delay + a callback\n\ttimeout(function) // Set the timeout callback\n\ttimeout(false) // Disable timeouts\n\n\n```javascript\nasyncChainable()\n\t.timeout(100, function() {\n\t\tconsole.log('Timer went over 100ms!');\n\t})\n\t.then(function(next) {\n\t\tsetTimeout(next, 2000); // Take 2 seconds\n\t})\n\t.end()\n```\n\nNOTE: Timeouts will also fire the `timeout` hook if the timeout function is the default (i.e. the user hasn't changed the function to their own).\n\n\n.then()\n-------\nExecute a function, wait for it to complete and continue down the asyncChainable chain.\n\nThis function is an alias for `series()`.\n\nThis can be considered the series process twin to `then()`.\n\n```javascript\nasyncChainable()\n\t.then('foo', fooFunc) // Execute and wait for fooFunc() to complete\n\t.then('bar', barFunc) // Likewise barFunc()\n\t.then('baz', bazFunc) // Likewise bazFunc()\n\t.end(console.log) // Output: null, {foo: 'foo value', bar: 'bar value', baz: 'baz value'}\n```\n\n\nContext\n=======\nUnless overridden by a call to `.context()`, async-chainable will use its own context object which can be accessed via `this` inside any callback function.\nThe context contains the results of any *named* functions as well as some meta data.\n\n```javascript\nasyncChainable()\n\t.series('foo', fooFunc)\n\t.defer('foo', fooFunc) // Execute fooFunc() and immediately move on\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await('foo', 'bar') // Wait for 'foo' and 'bar' parallel functions to finish but we dont care about 'baz' yet\n\t.then(function(next) {\n\t\tconsole.log('Context is', this); // Output 'Context is', {foo: 'foo value', bar: 'bar value', /* META FIELDS */}\n\t\tnext();\n\t})\n\t.await('baz')\n\t.end(function(next) {\n\t\tconsole.log('Context is', this); // Output 'Context is', {foo: 'foo value', bar: 'bar value', baz: 'baz value', /* META FIELDS */}\n\t\tnext();\n\t});\n```\n\n\nIn addition to storing all named values the context object also provides the following meta object values.\n\n| Key                                  | Type           |  Description                                                             |\n|--------------------------------------|----------------|--------------------------------------------------------------------------|\n| `this._struct`                       | Collection     | The structure of the async chain constructed by the developer            |\n| `this._structPointer`                | Int            | Offset in the `this._struct` collection as to the current executing function. Change this if you wish to move up and down |\n| `this._options`                      | Object         | Various options used by async-chainable including things like the defer limit |\n| `this._deferredRunning`              | Int            | The number of running deferred tasks (limit this using .limit())         |\n| `this._item`                         | Mixed          | During a forEach loop `_item` gets set to the currently iterating item value |\n| `this._key`                          | Mixed          | During a forEach loop `_key` gets set to the currently iterating array offset or object key |\n| `this._id`                           | Mixed          | During a defer call `_id` gets set to the currently defered task id      |\n| `this.fire`                          | Function       | Utility function used to manually fire hooks                             |\n| `this.hook`                          | Function       | Utility function used to manually register a hook                        |\n\n\nEach item in the `this._struct` object is composed of the following keys:\n\n\n| Key                                  | Type           |  Description                                                             |\n|--------------------------------------|----------------|--------------------------------------------------------------------------|\n| `completed`                          | Boolean        | An indicator as to whether this item has been executed yet               |\n| `payload`                            | Mixed          | The options for the item, in parallel or series modes this is an array or object of the tasks to execute |\n| `type`                               | String         | A supported internal execution type                                      |\n| `waitingOn`                          | Int            | When the type is a defer operation this integer tracks the number of defers that have yet to resolve |\n\n\nGotchas\n=======\nA list of some common errors when using async-chainable.\n\n\nForgetting a final `await()` when using `end()`\n-----------------------------------------------\n\nBy default async-chainable will not *imply* an `.await()` call before each `.end()` call. For example:\n\n```javascript\nasyncChainable()\n\t.defer('foo', fooFunc)\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.end(console.log);\n```\n\nIn the above no `.await()` call is made before `.end()` so this chain will *immediately* complete - async-chainable will **not** wait for the deferred tasks to complete.\n\n```javascript\nasyncChainable()\n\t.defer('foo', fooFunc)\n\t.defer('bar', barFunc)\n\t.defer('baz', bazFunc)\n\t.await()\n\t.end(console.log);\n```\n\nIn the above async-chainable will wait for the deferred tasks to complete before firing the end condition.\n\n\nForgetting the `()` when initializing\n-------------------------------------\nAsync-chainable needs to store state as it processes the task stack, to do this it instantiates itself as an object. This means you must declare it with an additional `()` after the `require()` statement if you wish to use it straight away. For example:\n\n\n```javascript\nvar asyncChainable = require('async-chainable')(); // NOTE '()'\n\nasyncChainable()\n\t.parallel([fooFunc, barFunc, bazFunc])\n\t.series([fooFunc, barFunc, bazFunc])\n\t.end(console.log)\n```\n\nIf you want to use multiple instances you can use either:\n\n```javascript\nvar asyncChainable = require('async-chainable'); // NOTE: this returns the libray not the instance\n\nasyncChainable()\n\t.parallel([fooFunc, barFunc, bazFunc])\n\t.series([fooFunc, barFunc, bazFunc])\n\t.end(console.log)\n\nasyncChainable()\n\t.parallel([fooFunc, barFunc, bazFunc])\n\t.series([fooFunc, barFunc, bazFunc])\n\n\t.end(console.log)\n```\n\nIts annoying we have to do this but without hacking around how Nodes module system works its not possible to return a singleton object like the async library does *and also* work with nested instances (i.e. having one .js file require() another that uses async-chainable and the whole thing not end up in a messy stack trace as the second instance inherits the firsts return state).\n\n\nUseful techniques\n=================\n\nDebugging\n---------\nIf you find that async-chainable is hanging try setting a `.timeout()` on the object to be notified when something is taking a while.\n\nFor one off operations async-chainable will also respond to the `DEBUG=async-chainable` environment variable. For example running your script as:\n\n\tDEBUG=async-chainable node myscript.js\n\n... will automatically set a `.timeout(5000)` call on **all** async-chainable objects with the default timeout handler (which should give some useful information on anything that is hanging).\n\n\nMake a variable number of tasks then execute them\n-------------------------------------------------\nSince JavaScript passes everything via pointers you can pass in an array or object to a .parallel() or .series() call which will get evaluated only when that chain item gets executed. This means that preceding items can rewrite the actual tasks conducted during that call.\n\nFor example in the below `otherTasks` is an array which is passed into the .parallel() call (the second chain item). However the initial .then() callback actually writes the items that that parallel call should make.\n\n```javascript\nvar otherTasks = [];\n\nasyncChainable()\n\t.then(function(next) {\n\t\tfor (var i = 0; i \u003c 20; i++) {\n\t\t\t(function(i) { // Make a closure so 'i' doesnt end up being 20 all the time (since its passed by reference)\n\t\t\t\totherTasks.push(function(next) {\n\t\t\t\t\tconsole.log('Hello World', i);\n\t\t\t\t\tnext();\n\t\t\t\t});\n\t\t\t})(i);\n\t\t}\n\t\tnext();\n\t})\n\t.parallel(otherTasks)\n\t.end();\n```\n\nCompose an array of items then run each though a handler function\n-----------------------------------------------------------------\nLike the above example async-chainable can be used to prepare items for execution then thread them into a subsequent chain for processing.\nThis is a neater version of the above that uses a fixed processing function to process an array of data.\n\n\n```javascript\nvar asyncChainable = require('./index');\n\nvar items = [];\n\nasyncChainable()\n\t.then(function(next) {\n\t\tfor (var i = 0; i \u003c 20; i++) {\n\t\t\titems.push({text: 'Hello World ' + i});\n\t\t}\n\t\tnext();\n\t})\n\t.forEach(items, function(next, item) {\n\t\tconsole.log(item);\n\t\tnext();\n\t})\n\t.end();\n```\n","funding_links":[],"categories":["NodeJS","Packages","包"],"sub_categories":["Control flow Callbacks","Control flow"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhash-bang%2Fasync-chainable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhash-bang%2Fasync-chainable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhash-bang%2Fasync-chainable/lists"}