index.js 4.9 KB

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