/** * 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; }); }