UNPKG

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