├── .gitignore ├── .travis.yml ├── index.js ├── reject.js ├── resolve.js ├── HISTORY.md ├── all.js ├── package.json ├── test.js ├── chain.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.chain = require('./chain') 2 | exports.all = require('./all') 3 | -------------------------------------------------------------------------------- /reject.js: -------------------------------------------------------------------------------- 1 | module.exports = function reject (value, fn) { 2 | fn(value) 3 | } 4 | -------------------------------------------------------------------------------- /resolve.js: -------------------------------------------------------------------------------- 1 | module.exports = function resolve (value, fn) { 2 | fn(null, value) 3 | } 4 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## [v1.0.1] 2 | > Mar 26, 2016 3 | 4 | - Remove `tape` dependency. 5 | 6 | [v1.0.1]: https://github.com/rstacruz/async-then/compare/v1.0.0...v1.0.1 7 | 8 | ## [v1.0.0] 9 | > Mar 26, 2016 10 | 11 | - Initial release. 12 | 13 | [v1.0.0]: https://github.com/rstacruz/async-then/tree/v1.0.0 14 | 15 | -------------------------------------------------------------------------------- /all.js: -------------------------------------------------------------------------------- 1 | module.exports = function all (callbacks, next) { 2 | var results = [] 3 | var complete = 0 4 | var error 5 | 6 | for (var i = 0, len = callbacks.length; i < len; i++) { 7 | ;(function (i) { 8 | callbacks[i](function (err, res) { 9 | if (error) return 10 | if (err) { 11 | error = err 12 | next(err) 13 | } else { 14 | results[i] = res 15 | complete++ 16 | if (complete === len) next(null, results) 17 | } 18 | }) 19 | }(i)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-then", 3 | "description": "Manipulate asynchronous operations", 4 | "version": "1.0.1", 5 | "author": "Rico Sta. Cruz ", 6 | "bugs": { 7 | "url": "https://github.com/rstacruz/async-then/issues" 8 | }, 9 | "devDependencies": { 10 | "tape": "4.5.1" 11 | }, 12 | "homepage": "https://github.com/rstacruz/async-then#readme", 13 | "keywords": [ 14 | "async", 15 | "asynchronous", 16 | "promise" 17 | ], 18 | "license": "MIT", 19 | "main": "chain.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/rstacruz/async-then.git" 23 | }, 24 | "scripts": { 25 | "test": "node test.js" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | var chain = require('./chain') 4 | var all = require('./all') 5 | 6 | test('chain()', t => { 7 | t.plan(4) 8 | chain() 9 | .then((_, next) => { t.pass('runs'); next() }) 10 | .then((_, next) => { t.pass('runs again'); next() }) 11 | .then((_, next) => { next('oh no') }) 12 | .catch((err, next) => { 13 | t.equal(err, 'oh no', 'errors are raised') 14 | next() 15 | }) 16 | .then((_, next) => { 17 | t.pass('recovers after errors') 18 | next() 19 | }) 20 | .end(function (err, res) { 21 | if (err) throw err 22 | t.end() 23 | }) 24 | }) 25 | 26 | test('chain() sync', t => { 27 | t.plan(2) 28 | chain() 29 | .then(_ => 1) 30 | .then(_ => _ + 1) 31 | .end(function (err, res) { 32 | t.error(err, 'has no errors') 33 | t.equal(res, 2, 'chains properly') 34 | t.end() 35 | }) 36 | }) 37 | 38 | test('all()', t => { 39 | t.plan(2) 40 | all([ 41 | next => { next(null, 'a') }, 42 | next => { next(null, 'b') }, 43 | next => { next(null, 'c') } 44 | ], (err, res) => { 45 | t.error(err, 'has no error') 46 | t.deepEqual(res, ['a', 'b', 'c'], 'has correct output') 47 | t.end() 48 | }) 49 | }) 50 | 51 | test('all() with errors', t => { 52 | t.plan(1) 53 | all([ 54 | next => { next('oh no') }, 55 | next => { next('other error') } 56 | ], (err, res) => { 57 | t.equal(err, 'oh no', 'reports an error') 58 | t.end() 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /chain.js: -------------------------------------------------------------------------------- 1 | var resolve = require('./resolve') 2 | 3 | /** 4 | * Starts a chain. 5 | * 6 | * function getTitle (fn) { 7 | * chain() 8 | * .then((_, next) => { request('http://google.com', next) }) 9 | * .then((_, next) => { next(null, cheerio.load(_)) } 10 | * .end(fn) 11 | * } 12 | * 13 | * fn((err, $) => { 14 | * if (err) throw err 15 | * console.log($('h1').text()) 16 | * }) 17 | */ 18 | 19 | function chain () { 20 | return step(function (fn) { resolve(undefined, fn) }) 21 | } 22 | 23 | /** 24 | * Internal: produces an object with `then()`, `catch()` and `end()`. 25 | */ 26 | 27 | function step (thunk) { 28 | return { then: then, catch: catch_, end: thunk } 29 | 30 | function catch_ (fn) { 31 | return step(connectCatch(thunk, fn)) 32 | } 33 | 34 | function then (fn) { 35 | return step(connectThen(thunk, fn)) 36 | } 37 | } 38 | 39 | /** 40 | * Internal: Return a thunk (a function that takes in a `callback`). This thunk 41 | * invokes given `thunk`, then passes on its return value to `fn(result, 42 | * callback)`. 43 | * 44 | * `fn` is what is being passed onto .then(). 45 | */ 46 | 47 | function connectThen (thunk, fn) { 48 | return function (next) { 49 | try { 50 | thunk(function (err, result) { 51 | if (err) return next(err) 52 | fn.length === 1 ? next(null, fn(result)) : fn(result, next) 53 | }) 54 | } catch (err) { 55 | next(err) 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Internal: Return a thunk (a function that takes in a `callback`). This thunk 62 | * invokes given `thunk`, then passes on its error to `fn(error, callback)`. 63 | * 64 | * Like connectThen(), but handles errors. 65 | */ 66 | 67 | function connectCatch (thunk, fn) { 68 | return function (next) { 69 | try { 70 | thunk(function (err, result) { 71 | if (err) return fn(err, next) 72 | fn.length === 1 ? next(fn(result)) : fn(null, result) 73 | }) 74 | } catch (err) { 75 | fn(err, next) 76 | } 77 | } 78 | } 79 | 80 | module.exports = chain 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-then 2 | 3 | > Manipulate asynchronous operations 4 | 5 | Work with Node-style callbacks in a safe way. The API is modeled after Promises, while async-then doesn't need (or support) promises. 6 | 7 | __NB:__ _This is a proof-of-concept of applying Promise idioms to callbacks. This package won't likely be supported._ 8 | 9 | [![Status](https://travis-ci.org/rstacruz/async-then.svg?branch=master)](https://travis-ci.org/rstacruz/async-then "See test builds") 10 | 11 | [co]: https://github.com/tj/co 12 | 13 | ## Features 14 | 15 | ### Waterfall flow 16 | 17 | Using [chain()] and [then()], you'll be able to run async operations one after the other, giving the result on an operation to the next operation and so on. 18 | 19 | ```js 20 | function read (symlink, next) { 21 | chain() 22 | .then((_, next) => { fs.readlink(symlink, next) }) 23 | .then((real, next) => { fs.readdir(real, next) }) 24 | .then((data, next) => { next(null, data.map(d => path.join(symlink, d)) }) 25 | .end(next) 26 | } 27 | ``` 28 | 29 | For comparison, here it is without async-then: 30 | 31 | > ```js 32 | > function read (symlink, next) { 33 | > fs.readlink(path, (err, real) => { 34 | > if (err) return next(err) 35 | > fs.readdir(real, (err, data) => { 36 | > if (err) return next(err) 37 | > data = data.map(d => path.join(symlink, d)) 38 | > next(null, data) 39 | > }) 40 | > }) 41 | > } 42 | > ``` 43 | 44 | ### Error control 45 | 46 | Notice in the example above, error handling (`if (err) return next(err)`) is absent. Errors will skip through [then()] steps, moving onto the next [catch()] or [end()] instead. 47 | 48 | ```js 49 | chain() 50 | .then((_, next) => { fs.lstat(path) }) 51 | .catch((err) => { if (err !== 'ENOENT') throw err }) 52 | .then(...) 53 | .end(...) 54 | ``` 55 | 56 | ### At a glance 57 | 58 | * __No promises__ - async-then works with Node-style callbacks (`err, result`), and does _not_ support promises. It lets you work these kinds of operations in a way you would with Promises/A+ without actually using promises. 59 | 60 | * __No wrappers__ - unlike other solutions like [co][] v3, there's no need to wrap your callback-style functions into thunks or promise-generators. 61 | 62 | * __Error catching__ - no need for extraneous `if (err) throw err`. Error flow is managed like promises with [catch()]. 63 | 64 | ## API 65 | 66 | ### chain 67 | > `chain()` 68 | 69 | Starts a chain. Compare with `Promise.resolve()` or any other promise. Returns an object with [then()], [catch()] and [end()] methods. 70 | 71 | ```js 72 | var chain = require('async-then/chain') 73 | 74 | function getTitle (fn) { 75 | chain() 76 | .then((_, next) => { request('http://google.com', next) }) 77 | .then((data) => cheerio.load(data)) 78 | .then(($) => $('title').text())) 79 | .end(fn) 80 | } 81 | 82 | getTitle((err, title) => { 83 | if (err) throw err 84 | console.log(title) 85 | }) 86 | ``` 87 | 88 | ### chain().then 89 | > `chain().then(fn)` 90 | 91 | Continues a chain; queues up another function to run when previous `then()` calls complete. In the asynchronous form, the function `fn` should accept two parameters: `result`, `next`. 92 | 93 | #### Async 94 | When `fn` accepts 2 parameters (`result`, `next`), it's invoke asynchronously. The parameter `result` is the result of the previous operation. `next` is a function that should be invoked as a callback. 95 | 96 | ```js 97 | chain() 98 | .then((result, next) => { fs.readFile('url.txt', 'utf-8', next) }) 99 | .then((data, next) => { request(data, next) }) 100 | .end((err, res) => { ... }) 101 | ``` 102 | 103 | #### Synchronous form 104 | 105 | When `fn` only accepts 1 parameter (`result`), it's invoked synchronously. Whatever its return value will be the value passed to the next `then()` in the chain. 106 | 107 | ```js 108 | chain() 109 | .then((result, next) => { fs.readFile('url-list.txt', 'utf-8', next) }) 110 | .then((data) => { return data.trim().split('\n') }) 111 | .end((err, urls) => { 112 | /* work with urls */ 113 | }) 114 | ``` 115 | 116 | #### Errors 117 | Th `fn` function can either `throw` an error, or invoke `next` with an error. All errors will skip through the subsequent `then()` steps; it skips onto the next [catch()] or [end()]. 118 | 119 | ### chain().catch 120 | > `chain().catch(fn)` 121 | 122 | Catches errors. It works like [then()]. 123 | If a [catch()] operation succeeds (meaning it didn't `throw` an error, or invoke `next(err)`), it'll continue onto the next [then()] or [end()]. 124 | 125 | ```js 126 | chain() 127 | .then((_, next) => { fs.lstat(path) }) 128 | .catch((err) => { if (err !== 'ENOENT') throw err }) 129 | .end(...) 130 | ``` 131 | 132 | ### chain().end 133 | > `chain().end(fn)` 134 | 135 | Runs the chain. Without calling `.end(fn)`, the chain will not be called. The parameter `fn` is a callback that takes Node-style arguments: `err`, `result`. 136 | 137 | ```js 138 | chain() 139 | .then((_, next) => { fs.readFile('data.txt', 'utf-8', next) }) 140 | .end((err, data) => { 141 | }) 142 | ``` 143 | 144 | ### all 145 | > `all(callbacks, next)` 146 | 147 | Runs multiple async operations in parallel. Compare with `Promise.all()`. 148 | 149 | ```js 150 | var all = require('async-then/all') 151 | 152 | all([ 153 | next => { request('http://facebook.com', next) }, 154 | next => { request('http://instagram.com', next) }, 155 | next => { request('http://pinterest.com', next) } 156 | ], (err, results) => { 157 | // results is an array 158 | }) 159 | ``` 160 | 161 | [chain()]: #chain 162 | [then()]: #chainthen 163 | [catch()]: #chaincatch 164 | [end()]: #chainend 165 | [all()]: #all 166 | 167 | ## Thanks 168 | 169 | **async-then** © 2016+, Rico Sta. Cruz. Released under the [MIT] License.
170 | Authored and maintained by Rico Sta. Cruz with help from contributors ([list][contributors]). 171 | 172 | > [ricostacruz.com](http://ricostacruz.com)  ·  173 | > GitHub [@rstacruz](https://github.com/rstacruz)  ·  174 | > Twitter [@rstacruz](https://twitter.com/rstacruz) 175 | 176 | [MIT]: http://mit-license.org/ 177 | [contributors]: http://github.com/rstacruz/async-then/contributors 178 | --------------------------------------------------------------------------------