UNPKG

when

Version:

A lightweight Promises/A+ and when() implementation, plus other async goodies.

300 lines (256 loc) 8.37 kB
/** @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); }));