blob: 7eea6aaa5c593cfc66849ddc28496d3473c0bb13 [file] [log] [blame]
/** @license MIT License (c) copyright 2010-2014 original author or authors */
/** @author Brian Cavalier */
/** @author John Hann */
(function(define) { 'use strict';
define(function(require) {
var state = require('../state');
var applier = require('../apply');
return function array(Promise) {
var applyFold = applier(Promise);
var toPromise = Promise.resolve;
var all = Promise.all;
var ar = Array.prototype.reduce;
var arr = Array.prototype.reduceRight;
var slice = Array.prototype.slice;
// Additional array combinators
Promise.any = any;
Promise.some = some;
Promise.settle = settle;
Promise.map = map;
Promise.filter = filter;
Promise.reduce = reduce;
Promise.reduceRight = reduceRight;
/**
* When this promise fulfills with an array, do
* onFulfilled.apply(void 0, array)
* @param {function} onFulfilled function to apply
* @returns {Promise} promise for the result of applying onFulfilled
*/
Promise.prototype.spread = function(onFulfilled) {
return this.then(all).then(function(array) {
return onFulfilled.apply(this, array);
});
};
return Promise;
/**
* One-winner competitive race.
* Return a promise that will fulfill when one of the promises
* in the input array fulfills, or will reject when all promises
* have rejected.
* @param {array} promises
* @returns {Promise} promise for the first fulfilled value
*/
function any(promises) {
var p = Promise._defer();
var resolver = p._handler;
var l = promises.length>>>0;
var pending = l;
var errors = [];
for (var h, x, i = 0; i < l; ++i) {
x = promises[i];
if(x === void 0 && !(i in promises)) {
--pending;
continue;
}
h = Promise._handler(x);
if(h.state() > 0) {
resolver.become(h);
Promise._visitRemaining(promises, i, h);
break;
} else {
h.visit(resolver, handleFulfill, handleReject);
}
}
if(pending === 0) {
resolver.reject(new RangeError('any(): array must not be empty'));
}
return p;
function handleFulfill(x) {
/*jshint validthis:true*/
errors = null;
this.resolve(x); // this === resolver
}
function handleReject(e) {
/*jshint validthis:true*/
if(this.resolved) { // this === resolver
return;
}
errors.push(e);
if(--pending === 0) {
this.reject(errors);
}
}
}
/**
* N-winner competitive race
* Return a promise that will fulfill when n input promises have
* fulfilled, or will reject when it becomes impossible for n
* input promises to fulfill (ie when promises.length - n + 1
* have rejected)
* @param {array} promises
* @param {number} n
* @returns {Promise} promise for the earliest n fulfillment values
*
* @deprecated
*/
function some(promises, n) {
/*jshint maxcomplexity:7*/
var p = Promise._defer();
var resolver = p._handler;
var results = [];
var errors = [];
var l = promises.length>>>0;
var nFulfill = 0;
var nReject;
var x, i; // reused in both for() loops
// First pass: count actual array items
for(i=0; i<l; ++i) {
x = promises[i];
if(x === void 0 && !(i in promises)) {
continue;
}
++nFulfill;
}
// Compute actual goals
n = Math.max(n, 0);
nReject = (nFulfill - n + 1);
nFulfill = Math.min(n, nFulfill);
if(n > nFulfill) {
resolver.reject(new RangeError('some(): array must contain at least '
+ n + ' item(s), but had ' + nFulfill));
} else if(nFulfill === 0) {
resolver.resolve(results);
}
// Second pass: observe each array item, make progress toward goals
for(i=0; i<l; ++i) {
x = promises[i];
if(x === void 0 && !(i in promises)) {
continue;
}
Promise._handler(x).visit(resolver, fulfill, reject, resolver.notify);
}
return p;
function fulfill(x) {
/*jshint validthis:true*/
if(this.resolved) { // this === resolver
return;
}
results.push(x);
if(--nFulfill === 0) {
errors = null;
this.resolve(results);
}
}
function reject(e) {
/*jshint validthis:true*/
if(this.resolved) { // this === resolver
return;
}
errors.push(e);
if(--nReject === 0) {
results = null;
this.reject(errors);
}
}
}
/**
* Apply f to the value of each promise in a list of promises
* and return a new list containing the results.
* @param {array} promises
* @param {function(x:*, index:Number):*} f mapping function
* @returns {Promise}
*/
function map(promises, f) {
return Promise._traverse(f, promises);
}
/**
* Filter the provided array of promises using the provided predicate. Input may
* contain promises and values
* @param {Array} promises array of promises and values
* @param {function(x:*, index:Number):boolean} predicate filtering predicate.
* Must return truthy (or promise for truthy) for items to retain.
* @returns {Promise} promise that will fulfill with an array containing all items
* for which predicate returned truthy.
*/
function filter(promises, predicate) {
var a = slice.call(promises);
return Promise._traverse(predicate, a).then(function(keep) {
return filterSync(a, keep);
});
}
function filterSync(promises, keep) {
// Safe because we know all promises have fulfilled if we've made it this far
var l = keep.length;
var filtered = new Array(l);
for(var i=0, j=0; i<l; ++i) {
if(keep[i]) {
filtered[j++] = Promise._handler(promises[i]).value;
}
}
filtered.length = j;
return filtered;
}
/**
* Return a promise that will always fulfill with an array containing
* the outcome states of all input promises. The returned promise
* will never reject.
* @param {Array} promises
* @returns {Promise} promise for array of settled state descriptors
*/
function settle(promises) {
return all(promises.map(settleOne));
}
function settleOne(p) {
// Optimize the case where we get an already-resolved when.js promise
// by extracting its state:
var handler;
if (p instanceof Promise) {
// This is our own Promise type and we can reach its handler internals:
handler = p._handler.join();
}
if((handler && handler.state() === 0) || !handler) {
// Either still pending, or not a Promise at all:
return toPromise(p).then(state.fulfilled, state.rejected);
}
// The promise is our own, but it is already resolved. Take a shortcut.
// Since we're not actually handling the resolution, we need to disable
// rejection reporting.
handler._unreport();
return state.inspect(handler);
}
/**
* Traditional reduce function, similar to `Array.prototype.reduce()`, but
* input may contain promises and/or values, and reduceFunc
* may return either a value or a promise, *and* initialValue may
* be a promise for the starting value.
* @param {Array|Promise} promises array or promise for an array of anything,
* may contain a mix of promises and values.
* @param {function(accumulated:*, x:*, index:Number):*} f reduce function
* @returns {Promise} that will resolve to the final reduced value
*/
function reduce(promises, f /*, initialValue */) {
return arguments.length > 2 ? ar.call(promises, liftCombine(f), arguments[2])
: ar.call(promises, liftCombine(f));
}
/**
* Traditional reduce function, similar to `Array.prototype.reduceRight()`, but
* input may contain promises and/or values, and reduceFunc
* may return either a value or a promise, *and* initialValue may
* be a promise for the starting value.
* @param {Array|Promise} promises array or promise for an array of anything,
* may contain a mix of promises and values.
* @param {function(accumulated:*, x:*, index:Number):*} f reduce function
* @returns {Promise} that will resolve to the final reduced value
*/
function reduceRight(promises, f /*, initialValue */) {
return arguments.length > 2 ? arr.call(promises, liftCombine(f), arguments[2])
: arr.call(promises, liftCombine(f));
}
function liftCombine(f) {
return function(z, x, i) {
return applyFold(f, void 0, [z,x,i]);
};
}
};
});
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); }));