Handle completion and errors with elegance! Support for streams, callbacks, promises, child processes, async/await and sync functions. A drop-in replacement for async-done - pass 100% of its tests plus more
Table of Contents
Install
Install with npm
$ npm i always-done --save
Usage
For more use-cases see the tests
const fs = require('fs')
const alwaysDone = require('always-done')
alwaysDone((cb) => {
fs.readFile('./package.json', 'utf8', cb)
}, (err, res) => {
if (err) return console.error(err)
let json = JSON.parse(res)
console.log(json.name)
})
Background
Behind the scenes we use just good plain old try/catch
block. Sounds you strange? See what "hard" job is done on try-catch-callback and try-catch-core.
In the first one, we just calls a function inside try/catch and calls done
callback with error or result of that function.
About second one, there we wraps the done
callback with once and dezalgo to ensure it will be called in the next tick.
Here, in always-done
, we just give a callback
to that try-catch-core package and "listen" what is the result. Actually we not listening anything, we just make a few checks to understand what the incoming value is - promise, child process, stream, observable and etc.
Resolution
Nothing so magical. Try/catch block for most of the things works briliant. And on-stream-end module (which is drop-in replacement for end-of-stream) for streams and child processes.
back to top
API
Handle completion of fn
and optionally pass done
callback, otherwise it returns a thunk. If that thunk
does not accept function, it returns another thunk until you pass done
to it.
Params
<fn>
{Function}: function to be called[opts]
{Object}: optional options, such as context
and args
, passed to try-catch-core[opts.context]
{Object}: context to be passed to fn
[opts.args]
{Array}: custom argument(s) to be pass to fn
, given value is arrayified[opts.passCallback]
{Boolean}: pass true
if you want cb
to be passed to fn
args[done]
{Function}: on completionreturns
{Function}: thunk until you pass done
to that thunk
Example
var alwaysDone = require('always-done')
var options = {
context: { num: 123, bool: true }
args: [require('assert')]
}
alwaysDone(function (assert, next) {
assert.strictEqual(this.num, 123)
assert.strictEqual(this.bool, true)
next()
}, options, function (err) {
console.log(err, 'done')
})
alwaysDone(function (cb) {
cb(new Error('foo bar'))
}, function done (err) {
console.log(err)
})
back to top
Supports
Handles completion and errors of async/await, synchronous and asynchronous (callback) functions, also functions that returns streams, promises, child process and observables.
Handles async/await
completion
alwaysDone(async function () {
return await Promise.resolve('foobar')
}, function done (e, res) {
console.log(res)
})
back to top
Callbacks completion
var alwaysDone = require('always-done')
alwaysDone(function (cb) {
fs.readFile('./package.json', 'utf8', cb)
}, function done (err, res) {
if (err) return console.log(err)
var pkg = JSON.parse(res)
console.log(pkg.name)
})
back to top
Completion of synchronous functions
Returning a value
alwaysDone(function () {
return 123
}, function done (e, res) {
console.log(res)
})
Returning an error
alwaysDone(function () {
return new Error('qux bar')
}, function done (err) {
console.log(err.message)
})
back to top
Completion of Promises
Returning a resolved Promise
alwaysDone(function () {
return Promise.resolve(12345)
}, function done (e, res) {
console.log(res)
})
Returning a rejected Promise
alwaysDone(function () {
return Promise.reject(new Error('foo bar'))
}, function done (err) {
console.log(err.message)
})
back to top
Streams completion
Using on-stream-end and stream-exhaust
Unpiped streams
alwaysDone(function () {
return fs.createReadStream('./package.json')
}, function done (e) {
console.log('stream completed')
})
Failing unpiped streams
alwaysDone(function () {
return fs.createReadStream('foo bar')
}, function done (err) {
console.log(err.code)
console.log(err.message)
})
Failing piped streams
alwaysDone(function () {
var read = fs.createReadStream('foo bar')
return read.pipe(through2())
}, function done (err) {
console.log(err.code)
console.log(err.message)
})
back to top
Handles completion of Observables
Using .subscribe
method of the observable
Empty observable
var Observable = require('rx').Observable
alwaysDone(function () {
return Observable.empty()
}, function done (e, res) {
console.log(e, res)
})
Successful observable
alwaysDone(function () {
return Observable.return([1, 2, 3])
}, function done (e, res) {
console.log(res)
})
Failing observable
alwaysDone(function () {
return Observable.throw(new Error('observable error'))
}, function done (err) {
console.log(err.message)
})
back to top
Completion of Child Process
Basically, they are streams, so completion is handled using on-stream-end which is drop-in replacement for end-of-stream
Successful exec
var cp = require('child_process')
alwaysDone(function () {
return cp.exec('echo hello world')
}, function done (e, res) {
console.log(res)
})
Failing exec
var cp = require('child_process')
alwaysDone(function () {
return cp.exec('foo-bar-baz sasa')
}, function done (err) {
console.log(err.message)
})
Failing spawn
var cp = require('child_process')
alwaysDone(function () {
return cp.spawn('foo-bar-baz', ['hello world'])
}, function done (err) {
console.log(err.code)
})
back to top
Handling errors
uncaught exceptions
alwaysDone(function () {
foo
return 55
}, function (err) {
console.log(err.name)
})
thrown errors
alwaysDone(function () {
JSON.parse('{"foo":')
}, function (err) {
console.log(err)
})
back to top
Always completes
It may looks strange, but it's logical. If you pass empty function it just completes with undefined
result and null
error.
Example
alwaysDone(function () {}, function (err, res) {
console.log(err, res)
})
back to top
Passing custom context
var alwaysDone = require('always-done')
var opts = {
context: { foo: 'bar' }
}
alwaysDone(function () {
console.log(this.foo)
}, opts, function done () {
console.log('end')
})
back to top
Passing custom arguments
It may be strange, but this allows you to pass more arguments to that first function and the last argument always will be "callback" until fn
is async or sync but with passCallback: true
option.
var alwaysDone = require('always-done')
var options = {
args: [1, 2]
}
alwaysDone(function (a, b) {
console.log(arguments.length)
console.log(a)
console.log(b)
return a + b + 3
}, options, function done (e, res) {
console.log(res)
})
back to top
Returning a thunk
Can be used as thunkify lib without problems, just don't pass a done callback.
var fs = require('fs')
var alwaysDone = require('always-done')
var readFileThunk = alwaysDone(function (cb) {
fs.readFile('./package.json', cb)
})
readFileThunk(function done (err, res) {
console.log(err, res)
})
back to top
Related
Contributing
Pull requests and stars are always welcome. For bugs and feature requests, please create an issue.
But before doing anything, please read the CONTRIBUTING.md guidelines.