array.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /** @license MIT License (c) copyright 2010-2014 original author or authors */
  2. /** @author Brian Cavalier */
  3. /** @author John Hann */
  4. (function(define) { 'use strict';
  5. define(function(require) {
  6. var state = require('../state');
  7. var applier = require('../apply');
  8. return function array(Promise) {
  9. var applyFold = applier(Promise);
  10. var toPromise = Promise.resolve;
  11. var all = Promise.all;
  12. var ar = Array.prototype.reduce;
  13. var arr = Array.prototype.reduceRight;
  14. var slice = Array.prototype.slice;
  15. // Additional array combinators
  16. Promise.any = any;
  17. Promise.some = some;
  18. Promise.settle = settle;
  19. Promise.map = map;
  20. Promise.filter = filter;
  21. Promise.reduce = reduce;
  22. Promise.reduceRight = reduceRight;
  23. /**
  24. * When this promise fulfills with an array, do
  25. * onFulfilled.apply(void 0, array)
  26. * @param {function} onFulfilled function to apply
  27. * @returns {Promise} promise for the result of applying onFulfilled
  28. */
  29. Promise.prototype.spread = function(onFulfilled) {
  30. return this.then(all).then(function(array) {
  31. return onFulfilled.apply(this, array);
  32. });
  33. };
  34. return Promise;
  35. /**
  36. * One-winner competitive race.
  37. * Return a promise that will fulfill when one of the promises
  38. * in the input array fulfills, or will reject when all promises
  39. * have rejected.
  40. * @param {array} promises
  41. * @returns {Promise} promise for the first fulfilled value
  42. */
  43. function any(promises) {
  44. var p = Promise._defer();
  45. var resolver = p._handler;
  46. var l = promises.length>>>0;
  47. var pending = l;
  48. var errors = [];
  49. for (var h, x, i = 0; i < l; ++i) {
  50. x = promises[i];
  51. if(x === void 0 && !(i in promises)) {
  52. --pending;
  53. continue;
  54. }
  55. h = Promise._handler(x);
  56. if(h.state() > 0) {
  57. resolver.become(h);
  58. Promise._visitRemaining(promises, i, h);
  59. break;
  60. } else {
  61. h.visit(resolver, handleFulfill, handleReject);
  62. }
  63. }
  64. if(pending === 0) {
  65. resolver.reject(new RangeError('any(): array must not be empty'));
  66. }
  67. return p;
  68. function handleFulfill(x) {
  69. /*jshint validthis:true*/
  70. errors = null;
  71. this.resolve(x); // this === resolver
  72. }
  73. function handleReject(e) {
  74. /*jshint validthis:true*/
  75. if(this.resolved) { // this === resolver
  76. return;
  77. }
  78. errors.push(e);
  79. if(--pending === 0) {
  80. this.reject(errors);
  81. }
  82. }
  83. }
  84. /**
  85. * N-winner competitive race
  86. * Return a promise that will fulfill when n input promises have
  87. * fulfilled, or will reject when it becomes impossible for n
  88. * input promises to fulfill (ie when promises.length - n + 1
  89. * have rejected)
  90. * @param {array} promises
  91. * @param {number} n
  92. * @returns {Promise} promise for the earliest n fulfillment values
  93. *
  94. * @deprecated
  95. */
  96. function some(promises, n) {
  97. /*jshint maxcomplexity:7*/
  98. var p = Promise._defer();
  99. var resolver = p._handler;
  100. var results = [];
  101. var errors = [];
  102. var l = promises.length>>>0;
  103. var nFulfill = 0;
  104. var nReject;
  105. var x, i; // reused in both for() loops
  106. // First pass: count actual array items
  107. for(i=0; i<l; ++i) {
  108. x = promises[i];
  109. if(x === void 0 && !(i in promises)) {
  110. continue;
  111. }
  112. ++nFulfill;
  113. }
  114. // Compute actual goals
  115. n = Math.max(n, 0);
  116. nReject = (nFulfill - n + 1);
  117. nFulfill = Math.min(n, nFulfill);
  118. if(n > nFulfill) {
  119. resolver.reject(new RangeError('some(): array must contain at least '
  120. + n + ' item(s), but had ' + nFulfill));
  121. } else if(nFulfill === 0) {
  122. resolver.resolve(results);
  123. }
  124. // Second pass: observe each array item, make progress toward goals
  125. for(i=0; i<l; ++i) {
  126. x = promises[i];
  127. if(x === void 0 && !(i in promises)) {
  128. continue;
  129. }
  130. Promise._handler(x).visit(resolver, fulfill, reject, resolver.notify);
  131. }
  132. return p;
  133. function fulfill(x) {
  134. /*jshint validthis:true*/
  135. if(this.resolved) { // this === resolver
  136. return;
  137. }
  138. results.push(x);
  139. if(--nFulfill === 0) {
  140. errors = null;
  141. this.resolve(results);
  142. }
  143. }
  144. function reject(e) {
  145. /*jshint validthis:true*/
  146. if(this.resolved) { // this === resolver
  147. return;
  148. }
  149. errors.push(e);
  150. if(--nReject === 0) {
  151. results = null;
  152. this.reject(errors);
  153. }
  154. }
  155. }
  156. /**
  157. * Apply f to the value of each promise in a list of promises
  158. * and return a new list containing the results.
  159. * @param {array} promises
  160. * @param {function(x:*, index:Number):*} f mapping function
  161. * @returns {Promise}
  162. */
  163. function map(promises, f) {
  164. return Promise._traverse(f, promises);
  165. }
  166. /**
  167. * Filter the provided array of promises using the provided predicate. Input may
  168. * contain promises and values
  169. * @param {Array} promises array of promises and values
  170. * @param {function(x:*, index:Number):boolean} predicate filtering predicate.
  171. * Must return truthy (or promise for truthy) for items to retain.
  172. * @returns {Promise} promise that will fulfill with an array containing all items
  173. * for which predicate returned truthy.
  174. */
  175. function filter(promises, predicate) {
  176. var a = slice.call(promises);
  177. return Promise._traverse(predicate, a).then(function(keep) {
  178. return filterSync(a, keep);
  179. });
  180. }
  181. function filterSync(promises, keep) {
  182. // Safe because we know all promises have fulfilled if we've made it this far
  183. var l = keep.length;
  184. var filtered = new Array(l);
  185. for(var i=0, j=0; i<l; ++i) {
  186. if(keep[i]) {
  187. filtered[j++] = Promise._handler(promises[i]).value;
  188. }
  189. }
  190. filtered.length = j;
  191. return filtered;
  192. }
  193. /**
  194. * Return a promise that will always fulfill with an array containing
  195. * the outcome states of all input promises. The returned promise
  196. * will never reject.
  197. * @param {Array} promises
  198. * @returns {Promise} promise for array of settled state descriptors
  199. */
  200. function settle(promises) {
  201. return all(promises.map(settleOne));
  202. }
  203. function settleOne(p) {
  204. // Optimize the case where we get an already-resolved when.js promise
  205. // by extracting its state:
  206. var handler;
  207. if (p instanceof Promise) {
  208. // This is our own Promise type and we can reach its handler internals:
  209. handler = p._handler.join();
  210. }
  211. if((handler && handler.state() === 0) || !handler) {
  212. // Either still pending, or not a Promise at all:
  213. return toPromise(p).then(state.fulfilled, state.rejected);
  214. }
  215. // The promise is our own, but it is already resolved. Take a shortcut.
  216. // Since we're not actually handling the resolution, we need to disable
  217. // rejection reporting.
  218. handler._unreport();
  219. return state.inspect(handler);
  220. }
  221. /**
  222. * Traditional reduce function, similar to `Array.prototype.reduce()`, but
  223. * input may contain promises and/or values, and reduceFunc
  224. * may return either a value or a promise, *and* initialValue may
  225. * be a promise for the starting value.
  226. * @param {Array|Promise} promises array or promise for an array of anything,
  227. * may contain a mix of promises and values.
  228. * @param {function(accumulated:*, x:*, index:Number):*} f reduce function
  229. * @returns {Promise} that will resolve to the final reduced value
  230. */
  231. function reduce(promises, f /*, initialValue */) {
  232. return arguments.length > 2 ? ar.call(promises, liftCombine(f), arguments[2])
  233. : ar.call(promises, liftCombine(f));
  234. }
  235. /**
  236. * Traditional reduce function, similar to `Array.prototype.reduceRight()`, but
  237. * input may contain promises and/or values, and reduceFunc
  238. * may return either a value or a promise, *and* initialValue may
  239. * be a promise for the starting value.
  240. * @param {Array|Promise} promises array or promise for an array of anything,
  241. * may contain a mix of promises and values.
  242. * @param {function(accumulated:*, x:*, index:Number):*} f reduce function
  243. * @returns {Promise} that will resolve to the final reduced value
  244. */
  245. function reduceRight(promises, f /*, initialValue */) {
  246. return arguments.length > 2 ? arr.call(promises, liftCombine(f), arguments[2])
  247. : arr.call(promises, liftCombine(f));
  248. }
  249. function liftCombine(f) {
  250. return function(z, x, i) {
  251. return applyFold(f, void 0, [z,x,i]);
  252. };
  253. }
  254. };
  255. });
  256. }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));