295 lines
5.3 KiB
JavaScript
295 lines
5.3 KiB
JavaScript
|
|
||
|
/**
|
||
|
* slice() reference.
|
||
|
*/
|
||
|
|
||
|
var slice = Array.prototype.slice;
|
||
|
|
||
|
/**
|
||
|
* Expose `co`.
|
||
|
*/
|
||
|
|
||
|
module.exports = co;
|
||
|
|
||
|
/**
|
||
|
* Wrap the given generator `fn` and
|
||
|
* return a thunk.
|
||
|
*
|
||
|
* @param {Function} fn
|
||
|
* @return {Function}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
function co(fn) {
|
||
|
var isGenFun = isGeneratorFunction(fn);
|
||
|
|
||
|
return function (done) {
|
||
|
var ctx = this;
|
||
|
|
||
|
// in toThunk() below we invoke co()
|
||
|
// with a generator, so optimize for
|
||
|
// this case
|
||
|
var gen = fn;
|
||
|
|
||
|
// we only need to parse the arguments
|
||
|
// if gen is a generator function.
|
||
|
if (isGenFun) {
|
||
|
var args = slice.call(arguments), len = args.length;
|
||
|
var hasCallback = len && 'function' == typeof args[len - 1];
|
||
|
done = hasCallback ? args.pop() : error;
|
||
|
gen = fn.apply(this, args);
|
||
|
} else {
|
||
|
done = done || error;
|
||
|
}
|
||
|
|
||
|
next();
|
||
|
|
||
|
// #92
|
||
|
// wrap the callback in a setImmediate
|
||
|
// so that any of its errors aren't caught by `co`
|
||
|
function exit(err, res) {
|
||
|
setImmediate(function(){
|
||
|
done.call(ctx, err, res);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function next(err, res) {
|
||
|
var ret;
|
||
|
|
||
|
// multiple args
|
||
|
if (arguments.length > 2) res = slice.call(arguments, 1);
|
||
|
|
||
|
// error
|
||
|
if (err) {
|
||
|
try {
|
||
|
ret = gen.throw(err);
|
||
|
} catch (e) {
|
||
|
return exit(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ok
|
||
|
if (!err) {
|
||
|
try {
|
||
|
ret = gen.next(res);
|
||
|
} catch (e) {
|
||
|
return exit(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// done
|
||
|
if (ret.done) return exit(null, ret.value);
|
||
|
|
||
|
// normalize
|
||
|
ret.value = toThunk(ret.value, ctx);
|
||
|
|
||
|
// run
|
||
|
if ('function' == typeof ret.value) {
|
||
|
var called = false;
|
||
|
try {
|
||
|
ret.value.call(ctx, function(){
|
||
|
if (called) return;
|
||
|
called = true;
|
||
|
next.apply(ctx, arguments);
|
||
|
});
|
||
|
} catch (e) {
|
||
|
setImmediate(function(){
|
||
|
if (called) return;
|
||
|
called = true;
|
||
|
next(e);
|
||
|
});
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// invalid
|
||
|
next(new TypeError('You may only yield a function, promise, generator, array, or object, '
|
||
|
+ 'but the following was passed: "' + String(ret.value) + '"'));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert `obj` into a normalized thunk.
|
||
|
*
|
||
|
* @param {Mixed} obj
|
||
|
* @param {Mixed} ctx
|
||
|
* @return {Function}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function toThunk(obj, ctx) {
|
||
|
|
||
|
if (isGeneratorFunction(obj)) {
|
||
|
return co(obj.call(ctx));
|
||
|
}
|
||
|
|
||
|
if (isGenerator(obj)) {
|
||
|
return co(obj);
|
||
|
}
|
||
|
|
||
|
if (isPromise(obj)) {
|
||
|
return promiseToThunk(obj);
|
||
|
}
|
||
|
|
||
|
if ('function' == typeof obj) {
|
||
|
return obj;
|
||
|
}
|
||
|
|
||
|
if (isObject(obj) || Array.isArray(obj)) {
|
||
|
return objectToThunk.call(ctx, obj);
|
||
|
}
|
||
|
|
||
|
return obj;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert an object of yieldables to a thunk.
|
||
|
*
|
||
|
* @param {Object} obj
|
||
|
* @return {Function}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function objectToThunk(obj){
|
||
|
var ctx = this;
|
||
|
var isArray = Array.isArray(obj);
|
||
|
|
||
|
return function(done){
|
||
|
var keys = Object.keys(obj);
|
||
|
var pending = keys.length;
|
||
|
var results = isArray
|
||
|
? new Array(pending) // predefine the array length
|
||
|
: new obj.constructor();
|
||
|
var finished;
|
||
|
|
||
|
if (!pending) {
|
||
|
setImmediate(function(){
|
||
|
done(null, results)
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// prepopulate object keys to preserve key ordering
|
||
|
if (!isArray) {
|
||
|
for (var i = 0; i < pending; i++) {
|
||
|
results[keys[i]] = undefined;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < keys.length; i++) {
|
||
|
run(obj[keys[i]], keys[i]);
|
||
|
}
|
||
|
|
||
|
function run(fn, key) {
|
||
|
if (finished) return;
|
||
|
try {
|
||
|
fn = toThunk(fn, ctx);
|
||
|
|
||
|
if ('function' != typeof fn) {
|
||
|
results[key] = fn;
|
||
|
return --pending || done(null, results);
|
||
|
}
|
||
|
|
||
|
fn.call(ctx, function(err, res){
|
||
|
if (finished) return;
|
||
|
|
||
|
if (err) {
|
||
|
finished = true;
|
||
|
return done(err);
|
||
|
}
|
||
|
|
||
|
results[key] = res;
|
||
|
--pending || done(null, results);
|
||
|
});
|
||
|
} catch (err) {
|
||
|
finished = true;
|
||
|
done(err);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert `promise` to a thunk.
|
||
|
*
|
||
|
* @param {Object} promise
|
||
|
* @return {Function}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function promiseToThunk(promise) {
|
||
|
return function(fn){
|
||
|
promise.then(function(res) {
|
||
|
fn(null, res);
|
||
|
}, fn);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if `obj` is a promise.
|
||
|
*
|
||
|
* @param {Object} obj
|
||
|
* @return {Boolean}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function isPromise(obj) {
|
||
|
return obj && 'function' == typeof obj.then;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if `obj` is a generator.
|
||
|
*
|
||
|
* @param {Mixed} obj
|
||
|
* @return {Boolean}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function isGenerator(obj) {
|
||
|
return obj && 'function' == typeof obj.next && 'function' == typeof obj.throw;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if `obj` is a generator function.
|
||
|
*
|
||
|
* @param {Mixed} obj
|
||
|
* @return {Boolean}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function isGeneratorFunction(obj) {
|
||
|
return obj && obj.constructor && 'GeneratorFunction' == obj.constructor.name;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check for plain object.
|
||
|
*
|
||
|
* @param {Mixed} val
|
||
|
* @return {Boolean}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function isObject(val) {
|
||
|
return val && Object == val.constructor;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Throw `err` in a new stack.
|
||
|
*
|
||
|
* This is used when co() is invoked
|
||
|
* without supplying a callback, which
|
||
|
* should only be for demonstrational
|
||
|
* purposes.
|
||
|
*
|
||
|
* @param {Error} err
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function error(err) {
|
||
|
if (!err) return;
|
||
|
setImmediate(function(){
|
||
|
throw err;
|
||
|
});
|
||
|
}
|