@joshwillik/task
Advanced tools
Comparing version 1.0.1 to 1.1.0
module.exports = task | ||
const ERR_NO_UNBOUND = "task: callback is not bound correctly. Are you passing 'this.continue' as a callback instead of 'this.continue_cb'?" | ||
const ERR_NO_WAITER = 'task: cannot continue/throw, .wait() has not been called yet' | ||
@@ -7,4 +9,8 @@ async function task(fn) { | ||
catch(fn) { this._catch = fn }, | ||
throw(e) { this._wait.reject(e) }, | ||
_wait: null, | ||
_ensure_wait() { | ||
if (!this._wait) { | ||
throw new Error(ERR_NO_WAITER) | ||
} | ||
}, | ||
wait() { | ||
@@ -14,13 +20,33 @@ return new Promise((resolve, reject) => | ||
}, | ||
continue(v) { this._wait.resolve(v) }, | ||
_finally: [], | ||
finally(fn) { this._finally.push(fn) }, | ||
} | ||
context.throw = function(err) { | ||
if (!this._ensure_wait) { | ||
throw new Error(ERR_NO_UNBOUND) | ||
} | ||
this._ensure_wait() | ||
this._wait.reject(err) | ||
} | ||
context.continue = function(value) { | ||
if (!this._ensure_wait) { | ||
throw new Error(ERR_NO_UNBOUND) | ||
} | ||
this._ensure_wait() | ||
this._wait.resolve(value) | ||
} | ||
context.continue_cb = (err, value) => { | ||
if (err) { | ||
context.throw(err) | ||
} else { | ||
context.continue(value) | ||
} | ||
} | ||
try { | ||
return await fn.call(context) | ||
} catch(e) { | ||
} catch(err) { | ||
if (context._catch) { | ||
return await context._catch(e) | ||
return await context._catch(err) | ||
} else { | ||
throw e | ||
throw err | ||
} | ||
@@ -27,0 +53,0 @@ } finally { |
{ | ||
"name": "@joshwillik/task", | ||
"version": "1.0.1", | ||
"version": "1.1.0", | ||
"description": "async function++", | ||
@@ -5,0 +5,0 @@ "main": "lib/task.js", |
@@ -41,5 +41,8 @@ ## Installation | ||
}) | ||
// altertatively, you can do this instead: | ||
// fs.mkdir(scratch_dir, this.continue_cb) | ||
await this.wait() | ||
// will always run at the end of the task | ||
// will always run at the end of the task, even if something throws | ||
this.finally(() => fs.rmdir(scratch_dir)) | ||
// as proven by this code which randomly throws | ||
if (Math.round(Math.random())) { | ||
@@ -52,2 +55,4 @@ console.log('success') | ||
console.log('task done') | ||
}).catch(err => { | ||
console.log('task failed, but scratch_dir is still gone') | ||
}) | ||
@@ -64,1 +69,49 @@ | ||
``` | ||
## API | ||
### this.wait() | ||
`this.wait()` returns a promise that will be resolved when `this.continue()` | ||
or `this.throw()` is called. | ||
### this.continue([value]) | ||
`this.continue()` resolves a the promise that `this.wait()` returns. | ||
### this.throw(err) | ||
`this.throw(err)` rejects a the promise that `this.wait()` returns. | ||
### this.continue_cb(err, value) | ||
`this.continue_cb` can be used to avoid boilerplate code like the following: | ||
``` | ||
await task(async function() { | ||
fs.readFile('filename.txt', (err, value) => { | ||
if (err) { | ||
this.throw(err) | ||
} else { | ||
this.continue(value) | ||
} | ||
}) | ||
let file_data = await this.wait() | ||
console.log('file data', file_data) | ||
}) | ||
``` | ||
by replacing it with: | ||
``` | ||
await task(async function() { | ||
fs.readFile('filename.txt', this.continue_cb) | ||
let file_data = await this.wait() | ||
console.log('file data', file_data) | ||
}) | ||
``` | ||
### task.sleep(ms) | ||
A utility function that returns a promise that will resolve after `ms` | ||
milliseconds. |
47
test.js
@@ -5,2 +5,4 @@ import test from 'ava'; | ||
test('task', async t => { | ||
t.plan(7) | ||
// normal case | ||
@@ -23,2 +25,47 @@ const wait_for = v => new Promise(resolve => { | ||
// continue_cb | ||
function resolve_fn(should_fail, fn) { | ||
if (!fn) { | ||
throw Error('not a fn') | ||
} | ||
setImmediate(function() { | ||
if (should_fail) { | ||
fn(new Error('An error happened'), null) | ||
} else { | ||
fn(null, 'a success value') | ||
} | ||
}) | ||
} | ||
let continue_success_v = await task(async function() { | ||
resolve_fn(false, this.continue_cb) | ||
return await this.wait() | ||
}) | ||
t.deepEqual(continue_success_v, 'a success value') | ||
let continue_throw_v = task(async function() { | ||
resolve_fn(true, this.continue_cb) | ||
return await this.wait() | ||
}) | ||
t.throws(continue_throw_v) | ||
// warn about wait not being called yet | ||
t.throws(task(async function() { | ||
this.continue() | ||
}), /\.wait\(\) has not been called/) | ||
// warn about passing task.continue instead of task.continue_cb | ||
function test_unbound_continue(cb, actually_done) { | ||
setImmediate(() => { | ||
t.throws(() => { | ||
cb() | ||
}, /instead of 'this\.continue_cb'/) | ||
actually_done() | ||
}) | ||
} | ||
await task(async function() { | ||
test_unbound_continue(this.continue, () => { | ||
this.continue() | ||
}) | ||
return await this.wait() | ||
}) | ||
// catch | ||
@@ -25,0 +72,0 @@ let fail_v = await task(async function() { |
6828
144
115