index.js
1 2 /** 3 * slice() reference. 4 */ 5 6 var slice = Array.prototype.slice; 7 8 /** 9 * Expose `co`. 10 */ 11 12 module.exports = co['default'] = co.co = co; 13 14 /** 15 * Wrap the given generator `fn` into a 16 * function that returns a promise. 17 * This is a separate function so that 18 * every `co()` call doesn't create a new, 19 * unnecessary closure. 20 * 21 * @param {GeneratorFunction} fn 22 * @return {Function} 23 * @api public 24 */ 25 26 co.wrap = function (fn) { 27 createPromise.__generatorFunction__ = fn; 28 return createPromise; 29 function createPromise() { 30 return co.call(this, fn.apply(this, arguments)); 31 } 32 }; 33 34 /** 35 * Execute the generator function or a generator 36 * and return a promise. 37 * 38 * @param {Function} fn 39 * @return {Promise} 40 * @api public 41 */ 42 43 function co(gen) { 44 var ctx = this; 45 var args = slice.call(arguments, 1) 46 47 // we wrap everything in a promise to avoid promise chaining, 48 // which leads to memory leak errors. 49 // see https://github.com/tj/co/issues/180 50 return new Promise(function(resolve, reject) { 51 if (typeof gen === 'function') gen = gen.apply(ctx, args); 52 if (!gen || typeof gen.next !== 'function') return resolve(gen); 53 54 onFulfilled(); 55 56 /** 57 * @param {Mixed} res 58 * @return {Promise} 59 * @api private 60 */ 61 62 function onFulfilled(res) { 63 var ret; 64 try { 65 ret = gen.next(res); 66 } catch (e) { 67 return reject(e); 68 } 69 next(ret); 70 } 71 72 /** 73 * @param {Error} err 74 * @return {Promise} 75 * @api private 76 */ 77 78 function onRejected(err) { 79 var ret; 80 try { 81 ret = gen.throw(err); 82 } catch (e) { 83 return reject(e); 84 } 85 next(ret); 86 } 87 88 /** 89 * Get the next value in the generator, 90 * return a promise. 91 * 92 * @param {Object} ret 93 * @return {Promise} 94 * @api private 95 */ 96 97 function next(ret) { 98 if (ret.done) return resolve(ret.value); 99 var value = toPromise.call(ctx, ret.value); 100 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 101 return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' 102 + 'but the following object was passed: "' + String(ret.value) + '"')); 103 } 104 }); 105 } 106 107 /** 108 * Convert a `yield`ed value into a promise. 109 * 110 * @param {Mixed} obj 111 * @return {Promise} 112 * @api private 113 */ 114 115 function toPromise(obj) { 116 if (!obj) return obj; 117 if (isPromise(obj)) return obj; 118 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); 119 if ('function' == typeof obj) return thunkToPromise.call(this, obj); 120 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); 121 if (isObject(obj)) return objectToPromise.call(this, obj); 122 return obj; 123 } 124 125 /** 126 * Convert a thunk to a promise. 127 * 128 * @param {Function} 129 * @return {Promise} 130 * @api private 131 */ 132 133 function thunkToPromise(fn) { 134 var ctx = this; 135 return new Promise(function (resolve, reject) { 136 fn.call(ctx, function (err, res) { 137 if (err) return reject(err); 138 if (arguments.length > 2) res = slice.call(arguments, 1); 139 resolve(res); 140 }); 141 }); 142 } 143 144 /** 145 * Convert an array of "yieldables" to a promise. 146 * Uses `Promise.all()` internally. 147 * 148 * @param {Array} obj 149 * @return {Promise} 150 * @api private 151 */ 152 153 function arrayToPromise(obj) { 154 return Promise.all(obj.map(toPromise, this)); 155 } 156 157 /** 158 * Convert an object of "yieldables" to a promise. 159 * Uses `Promise.all()` internally. 160 * 161 * @param {Object} obj 162 * @return {Promise} 163 * @api private 164 */ 165 166 function objectToPromise(obj){ 167 var results = new obj.constructor(); 168 var keys = Object.keys(obj); 169 var promises = []; 170 for (var i = 0; i < keys.length; i++) { 171 var key = keys[i]; 172 var promise = toPromise.call(this, obj[key]); 173 if (promise && isPromise(promise)) defer(promise, key); 174 else results[key] = obj[key]; 175 } 176 return Promise.all(promises).then(function () { 177 return results; 178 }); 179 180 function defer(promise, key) { 181 // predefine the key in the result 182 results[key] = undefined; 183 promises.push(promise.then(function (res) { 184 results[key] = res; 185 })); 186 } 187 } 188 189 /** 190 * Check if `obj` is a promise. 191 * 192 * @param {Object} obj 193 * @return {Boolean} 194 * @api private 195 */ 196 197 function isPromise(obj) { 198 return 'function' == typeof obj.then; 199 } 200 201 /** 202 * Check if `obj` is a generator. 203 * 204 * @param {Mixed} obj 205 * @return {Boolean} 206 * @api private 207 */ 208 209 function isGenerator(obj) { 210 return 'function' == typeof obj.next && 'function' == typeof obj.throw; 211 } 212 213 /** 214 * Check if `obj` is a generator function. 215 * 216 * @param {Mixed} obj 217 * @return {Boolean} 218 * @api private 219 */ 220 function isGeneratorFunction(obj) { 221 var constructor = obj.constructor; 222 if (!constructor) return false; 223 if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; 224 return isGenerator(constructor.prototype); 225 } 226 227 /** 228 * Check for plain object. 229 * 230 * @param {Mixed} val 231 * @return {Boolean} 232 * @api private 233 */ 234 235 function isObject(val) { 236 return Object == val.constructor; 237 }