| /** |
| * toString() reference. |
| */ |
| |
| var toString = Object.prototype.toString; |
| |
| /** |
| * slice() reference. |
| */ |
| |
| var slice = Array.prototype.slice; |
| |
| /** |
| * setImmediate() with fallback to process.nextTick() for node v0.8.x. |
| */ |
| |
| var setImmediate = global.setImmediate || process.nextTick; |
| |
| /** |
| * 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(done.bind(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 Error('yield a function, promise, generator, array, or object')); |
| } |
| } |
| } |
| |
| /** |
| * 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; |
| |
| return function(done){ |
| var keys = Object.keys(obj); |
| var pending = keys.length; |
| var results = new obj.constructor(); |
| var finished; |
| |
| if (!pending) { |
| setImmediate(function(){ |
| done(null, results) |
| }); |
| return; |
| } |
| |
| 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; |
| }); |
| } |