286 lines
7.6 KiB
JavaScript
286 lines
7.6 KiB
JavaScript
|
/** @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) {
|
||
|
var h = Promise._handler(p);
|
||
|
return h.state() === 0 ? toPromise(p).then(state.fulfilled, state.rejected)
|
||
|
: state.inspect(h);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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); }));
|