Comparing version 1.0.0 to 1.1.0
73
index.js
@@ -1,9 +0,44 @@ | ||
var isGenerator = function (val) { | ||
function isGenerator (val) { | ||
return val && typeof val.next === 'function' && typeof val.throw === 'function' | ||
} | ||
var isPromise = function (val) { | ||
function isPromise (val) { | ||
return val && typeof val.then === 'function' | ||
} | ||
module.exports = function caco (gen) { | ||
function isObservable (val) { | ||
return val && typeof val.subscribe === 'function' | ||
} | ||
// default yieldable mapper | ||
function defaultMapper (val, cb) { | ||
if (isPromise(val)) { | ||
val.then(function (value) { | ||
cb(null, value) | ||
}, function (err) { | ||
cb(err || new Error()) | ||
}) | ||
return true | ||
} | ||
if (isGenerator(val)) { | ||
caco(val)(cb) | ||
return true | ||
} | ||
if (isObservable(val)) { | ||
var dispose = val.subscribe(function (res) { | ||
cb(null, res) | ||
dispose.dispose() | ||
}, function (err) { | ||
cb(err) | ||
dispose.dispose() | ||
}) | ||
return true | ||
} | ||
return false | ||
} | ||
function caco (gen, mapper) { | ||
return function () { | ||
@@ -15,3 +50,8 @@ var args = Array.prototype.slice.call(arguments) | ||
args.push(next) | ||
// callback stepper | ||
args.push(function next (err, res) { | ||
process.nextTick(function () { | ||
step(err, res) | ||
}) | ||
}) | ||
@@ -27,14 +67,7 @@ var iter = isGenerator(gen) ? gen : gen.apply(self, args) | ||
if (isPromise(state.value)) { | ||
// handle thenable | ||
state.value.then(function (value) { | ||
step(null, value) | ||
}, function (err) { | ||
step(err || true) | ||
}) | ||
} else if (isGenerator(state.value)) { | ||
caco(state.value)(next) | ||
} else if (state.done) { | ||
step(null, state.value) | ||
} | ||
var yieldable = defaultMapper(state.value, step) || ( | ||
mapper && mapper(state.value, step) | ||
) | ||
if (!yieldable && state.done) step(null, state.value) | ||
} catch (err) { | ||
@@ -46,8 +79,2 @@ // catch err, break iteration | ||
function next (err, res) { | ||
process.nextTick(function () { | ||
step(err, res) | ||
}) | ||
} | ||
if (callback) { | ||
@@ -67,1 +94,3 @@ step() | ||
} | ||
module.exports = caco |
{ | ||
"name": "caco", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Generator based control flow that supports both callbacks and promises", | ||
@@ -15,2 +15,3 @@ "scripts": { | ||
"devDependencies": { | ||
"rx": "^4.0.7", | ||
"standard": "^6.0.4", | ||
@@ -17,0 +18,0 @@ "tape": "^3.0.2" |
@@ -7,7 +7,8 @@ # caco | ||
Many of the existing async libraries require wrapping callback functions into promises to be usuable, which creates unnecessary complication. | ||
Many existing flow-control libraries such as [co](https://github.com/tj/co), assumes promises to be the lowest denominator of async handling. | ||
Callback functions require promisify to be compatible, which creates unnecessary complication. | ||
In caco, both callbacks and promises are 'yieldable'; | ||
resulting function can be used by both callbacks and promises. | ||
This enables a powerful control flow while maintaining compatibility. | ||
In caco, both callbacks and promises are yieldable. | ||
Resulting function can also be used by both callbacks and promises. | ||
This enables a powerful control flow while maintaining simplicity. | ||
@@ -18,2 +19,4 @@ ```bash | ||
#### var fn = caco(fn *) | ||
```js | ||
@@ -23,8 +26,4 @@ var caco = require('caco') | ||
var fn = caco(function * (next) { | ||
var foo = yield Promise.resolve('bar') // yield promise | ||
yield setTimeout(next, 100) // yield callback using 'next' argument | ||
// try/catch errors | ||
try { | ||
yield Promise.reject('boom') | ||
yield Promise.reject('boom') // yield promise reject throws error | ||
} catch (err) { | ||
@@ -34,38 +33,71 @@ console.log(err) // 'boom' | ||
var data = yield fs.readFile('./foo/bar', next) | ||
var foo = yield Promise.resolve('bar') // yield promise | ||
yield setTimeout(next, 1000) // yield callback using 'next' argument, delay 1 second | ||
// yield callback of form next(err, data): return data, throw if err exists | ||
var data = yield fs.readFile('./foo/bar', next) | ||
return data | ||
}) | ||
// consume with callback | ||
// Use with callback | ||
fn(function (err, res) { }) | ||
// Use with promise | ||
fn().then(...).catch(...) | ||
``` | ||
## API | ||
To enable yieldable callbacks, yielding non-promise-nor-generator value pauses the current generator. | ||
Until `next(err, val)` being invoked by callback, | ||
where `val` passes back to yielded value, or `throw` if `err` exists. | ||
#### var fn = caco(fn *) | ||
## Yieldables | ||
Wraps a generator into a regular function that acceots callback or promise. | ||
By default, the following objects are supported for `yield`: | ||
* `Promise` | ||
* `Observable` | ||
* `Generator` | ||
Caco also accepts a yield mapper function, | ||
so that one can basically yield anything. | ||
#### var fn = caco(fn *, mapper) | ||
```js | ||
var getN = caco(function * (n, next) { | ||
if (n === 689) yield Promise.reject('boom') // yield reject throws error | ||
return yield Promise.resolve(n) | ||
}) | ||
function mapper (val, cb) { | ||
// map array to Promise.all | ||
if (Array.isArray(val)) { | ||
Promise.all(val).then(function (res) { | ||
cb(null, res) | ||
}, cb) | ||
return true // acknowledge yieldable | ||
} | ||
getN(123, function (err, val) { | ||
console.log(val) // 123 | ||
}) | ||
getN(123).then(...) | ||
// Anything can be mapped! | ||
if (val === 689) { | ||
cb(new Error('DLLM')) | ||
return true | ||
} | ||
} | ||
getN(689).catch(function (err) { | ||
console.log(err) // boom | ||
}) | ||
caco(function * () { | ||
console.log(yield [ | ||
Promise.resolve(1), | ||
Promise.resolve(2), | ||
3 | ||
]) // [1, 2, 3] | ||
// yield 689 throws error | ||
try { | ||
yield 689 | ||
} catch (err) { | ||
console.log(err) // 'DLLM' | ||
} | ||
}, mapper)(function (err) { }) | ||
``` | ||
Generator accepts optional `next` argument for yieldable callback | ||
## License | ||
MIT |
130
test.js
var test = require('tape') | ||
var caco = require('./') | ||
var Observable = require('rx').Observable | ||
test('caco', function (t) { | ||
t.plan(5) | ||
test('arguments and return', function (t) { | ||
t.plan(8) | ||
function * v (n) { | ||
var fn = caco(function * (num, str, next) { | ||
t.equal(num, 167, 'arguemnt') | ||
t.equal(str, '167', 'arguemnt') | ||
t.equal(typeof next, 'function', 'stepping function') | ||
}) | ||
t.notOk(fn(167, '167', function () { }), 'passing callback returns undefined') | ||
t.equal(typeof fn(167, '167').then, 'function', 'no callback returns promise') | ||
}) | ||
test('scope', function (t) { | ||
var obj = {} | ||
caco(function * () { | ||
t.equal(this, obj, 'correct scope') | ||
t.end() | ||
}).call(obj) | ||
}) | ||
test('resolve and reject', function (t) { | ||
t.plan(6) | ||
caco(function * () { | ||
return 167 | ||
})().then(function (val) { | ||
t.equal(val, 167, 'promise resolve') | ||
}, t.error) | ||
caco(function * () { | ||
throw new Error('167') | ||
})().then(t.error, function (err) { | ||
t.equal(err.message, '167', 'promise reject') | ||
}) | ||
caco(function * () { | ||
return yield Promise.resolve(167) | ||
})(function (err, val) { | ||
t.error(err) | ||
t.equal(val, 167, 'callback value') | ||
}) | ||
caco(function * () { | ||
return Promise.reject(167) | ||
})(function (err, val) { | ||
t.equal(err, 167, 'callback error') | ||
t.error(val) | ||
}) | ||
}) | ||
test('default yieldable', function (t) { | ||
function * resolveGen (n) { | ||
return yield Promise.resolve(n) | ||
} | ||
var n = caco(function * (n) { | ||
var rejectFn = caco(function * (n) { | ||
return yield Promise.reject(n) | ||
}) | ||
var v1 = caco(function * (next) { | ||
yield setTimeout(next, 0) | ||
var instantVal = caco(function * (next) { | ||
return 1044 | ||
}) | ||
var v2 = caco(function * () { | ||
var tryCatch = caco(function * () { | ||
try { | ||
return yield n(689) | ||
} catch (e) { | ||
return yield v(167) | ||
return yield rejectFn(689) | ||
} catch (err) { | ||
t.equal(err, 689, 'try/catch promise reject') | ||
return yield resolveGen(167) | ||
} | ||
}) | ||
var f = caco(function * (str, next) { | ||
var n = (yield v1()) / 2 + (yield v2(next)) | ||
return str + n | ||
}) | ||
caco(function * (next) { | ||
yield setTimeout(next, 0) | ||
var o = yield Observable | ||
.fromArray([1, 2]) | ||
.merge(Observable.fromPromise(Promise.resolve(3))) | ||
.delay(10) | ||
.toArray() | ||
t.deepEqual(o, [1, 2, 3], 'yield observable') | ||
t.equal(yield instantVal(next), 1044, 'yield callback') | ||
t.equal(yield tryCatch(), 167, 'yield gnerator-promise') | ||
})(t.end) | ||
}) | ||
n('boom', function (err) { | ||
t.equal(err, 'boom', 'correct callback error') | ||
}) | ||
test('yieldable mapper', function (t) { | ||
caco(function * () { | ||
t.deepEqual(yield [ | ||
Promise.resolve(1), | ||
Promise.resolve(2), | ||
3 | ||
], [1, 2, 3], 'yield map array to Promise.all') | ||
n('boom').then(t.error).catch(function (err) { | ||
t.equal(err, 'boom', 'correct promise reject') | ||
}) | ||
f('D7', function (err, res) { | ||
t.notOk(err, 'no error') | ||
t.deepEqual(res, 'D7689', 'correct callback value') | ||
}) | ||
f('DLM').then(function (res) { | ||
t.deepEqual(res, 'DLM689', 'correct promise value') | ||
}).catch(t.error) | ||
try { | ||
yield 689 | ||
} catch (err) { | ||
t.equal(err.message, 'DLLM', 'yield 689 throws error') | ||
} | ||
}, function (val, cb) { | ||
// yield array | ||
if (Array.isArray(val)) { | ||
Promise.all(val).then(function (res) { | ||
cb(null, res) | ||
}, function (err) { | ||
cb(err || new Error()) | ||
}) | ||
return true | ||
} | ||
// yield 689 throws error | ||
if (val === 689) { | ||
cb(new Error('DLLM')) | ||
return true | ||
} | ||
})(t.end) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
8789
182
100
3