All files / framework/core/js FluidPromises.js

100% Statements 108/108
97.56% Branches 40/41
100% Functions 30/30
100% Lines 108/108
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267                  47x   47x                     47x 1997x           1997x 2357x 1963x 1579x   384x     2357x 2131x 103x   2028x     2357x   1997x 1889x 4x     1885x   1885x   1997x 110x 4x     106x   106x     1997x 1991x 1991x 1991x 305x     1997x           47x 2083x             47x 12x 8x   4x 4x 4x               47x 564x               47x 8x 8x 8x 6x 6x 2x   4x     2x   8x             47x 983x 2x   981x                     47x 1961x 1961x   1961x     47x 32x 54x 54x 54x   32x     47x 2942x 949x   1993x 1993x 1993x 224x 192x   32x     1769x             47x 10x   26x 26x     4x               47x 10x 8x 8x         47x 473x   1389x 1389x 1389x 1389x     445x             47x 473x   473x     473x 473x 473x 473x     47x 473x 469x   4x 8x                                     47x 473x 473x   473x 473x 473x          
/*!
 Copyright 2011 unscriptable.com / John Hann
 Copyright 2014 Lucendo Development Ltd.
 Copyright 2014 Raising the Floor - US
 Copyright 2014-2016 Raising the Floor - International
 
 License MIT
*/
 
var fluid_3_0_0 = fluid_3_0_0 || {};
 
(function ($, fluid) {
    "use strict";
 
// Light fluidification of minimal promises library. See original gist at
// https://gist.github.com/unscriptable/814052 for limitations and commentary
 
// This implementation provides what could be described as "flat promises" with
// no support for structured programming idioms involving promise composition.
// It provides what a proponent of mainstream promises would describe as
// a "glorified callback aggregator"
 
    fluid.promise = function () {
        var that = {
            onResolve: [],
            onReject: []
            // disposition
            // value
        };
        that.then = function (onResolve, onReject) {
            if (onResolve) {
                if (that.disposition === "resolve") {
                    onResolve(that.value);
                } else {
                    that.onResolve.push(onResolve);
                }
            }
            if (onReject) {
                if (that.disposition === "reject") {
                    onReject(that.value);
                } else {
                    that.onReject.push(onReject);
                }
            }
            return that;
        };
        that.resolve = function (value) {
            if (that.disposition) {
                fluid.fail("Error: resolving promise ", that,
                    " which has already received \"" + that.disposition + "\"");
            } else {
                that.complete("resolve", that.onResolve, value);
            }
            return that;
        };
        that.reject = function (reason) {
            if (that.disposition) {
                fluid.fail("Error: rejecting promise ", that,
                    "which has already received \"" + that.disposition + "\"");
            } else {
                that.complete("reject", that.onReject, reason);
            }
            return that;
        };
        // PRIVATE, NON-API METHOD
        that.complete = function (which, queue, arg) {
            that.disposition = which;
            that.value = arg;
            for (var i = 0; i < queue.length; ++i) {
                queue[i](arg);
            }
        };
        return that;
    };
 
    /* Any object with a member <code>then</code> of type <code>function</code> passes this test.
     * This includes essentially every known variety, including jQuery promises.
     */
    fluid.isPromise = function (totest) {
        return totest && typeof(totest.then) === "function";
    };
 
    /** Coerces any value to a promise
     * @param {Any} promiseOrValue - The value to be coerced
     * @return {Promise} - If the supplied value is already a promise, it is returned unchanged. Otherwise a fresh promise is created with the value as resolution and returned
     */
    fluid.toPromise = function (promiseOrValue) {
        if (fluid.isPromise(promiseOrValue)) {
            return promiseOrValue;
        } else {
            var togo = fluid.promise();
            togo.resolve(promiseOrValue);
            return togo;
        }
    };
 
    /* Chains the resolution methods of one promise (target) so that they follow those of another (source).
     * That is, whenever source resolves, target will resolve, or when source rejects, target will reject, with the
     * same payloads in each case.
     */
    fluid.promise.follow = function (source, target) {
        source.then(target.resolve, target.reject);
    };
 
    /** Returns a promise whose resolved value is mapped from the source promise or value by the supplied function.
     * @param {Object|Promise} source - An object or promise whose value is to be mapped
     * @param {Function} func - A function which will map the resolved promise value
     * @return {Promise} - A promise for the resolved mapped value.
     */
    fluid.promise.map = function (source, func) {
        var promise = fluid.toPromise(source);
        var togo = fluid.promise();
        promise.then(function (value) {
            var mapped = func(value);
            if (fluid.isPromise(mapped)) {
                fluid.promise.follow(mapped, togo);
            } else {
                togo.resolve(mapped);
            }
        }, function (error) {
            togo.reject(error);
        });
        return togo;
    };
 
    /* General skeleton for all sequential promise algorithms, e.g. transform, reduce, sequence, etc.
     * These accept a variable "strategy" pair to customise the interchange of values and final return
     */
 
    fluid.promise.makeSequencer = function (sources, options, strategy) {
        if (!fluid.isArrayable(sources)) {
            fluid.fail("fluid.promise sequence algorithms must be supplied an array as source");
        }
        return {
            sources: sources,
            resolvedSources: [], // the values of "sources" only with functions invoked (an array of promises or values)
            index: 0,
            strategy: strategy,
            options: options, // available to be supplied to each listener
            returns: [],
            promise: fluid.promise() // the final return value
        };
    };
 
    fluid.promise.progressSequence = function (that, retValue) {
        that.returns.push(retValue);
        that.index++;
        // No we dun't have no tail recursion elimination
        fluid.promise.resumeSequence(that);
    };
 
    fluid.promise.processSequenceReject = function (that, error) { // Allow earlier promises in the sequence to wrap the rejection supplied by later ones (FLUID-5584)
        for (var i = that.index - 1; i >= 0; --i) {
            var resolved = that.resolvedSources[i];
            var accumulator = fluid.isPromise(resolved) && typeof(resolved.accumulateRejectionReason) === "function" ? resolved.accumulateRejectionReason : fluid.identity;
            error = accumulator(error);
        }
        that.promise.reject(error);
    };
 
    fluid.promise.resumeSequence = function (that) {
        if (that.index === that.sources.length) {
            that.promise.resolve(that.strategy.resolveResult(that));
        } else {
            var value = that.strategy.invokeNext(that);
            that.resolvedSources[that.index] = value;
            if (fluid.isPromise(value)) {
                value.then(function (retValue) {
                    fluid.promise.progressSequence(that, retValue);
                }, function (error) {
                    fluid.promise.processSequenceReject(that, error);
                });
            } else {
                fluid.promise.progressSequence(that, value);
            }
        }
    };
 
    // SEQUENCE ALGORITHM APPLYING PROMISES
 
    fluid.promise.makeSequenceStrategy = function () {
        return {
            invokeNext: function (that) {
                var source = that.sources[that.index];
                return typeof(source) === "function" ? source(that.options) : source;
            },
            resolveResult: function (that) {
                return that.returns;
            }
        };
    };
 
    // accepts an array of values, promises or functions returning promises - in the case of functions returning promises,
    // will assure that at most one of these is "in flight" at a time - that is, the succeeding function will not be invoked
    // until the promise at the preceding position has resolved
    fluid.promise.sequence = function (sources, options) {
        var sequencer = fluid.promise.makeSequencer(sources, options, fluid.promise.makeSequenceStrategy());
        fluid.promise.resumeSequence(sequencer);
        return sequencer.promise;
    };
 
    // TRANSFORM ALGORITHM APPLYING PROMISES
 
    fluid.promise.makeTransformerStrategy = function () {
        return {
            invokeNext: function (that) {
                var lisrec = that.sources[that.index];
                lisrec.listener = fluid.event.resolveListener(lisrec.listener);
                var value = lisrec.listener.apply(null, [that.returns[that.index], that.options]);
                return value;
            },
            resolveResult: function (that) {
                return that.returns[that.index];
            }
        };
    };
 
    // Construct a "mini-object" managing the process of a sequence of transforms,
    // each of which may be synchronous or return a promise
    fluid.promise.makeTransformer = function (listeners, payload, options) {
        listeners.unshift({listener:
            function () {
                return payload;
            }
        });
        var sequencer = fluid.promise.makeSequencer(listeners, options, fluid.promise.makeTransformerStrategy());
        sequencer.returns.push(null); // first dummy return from initial entry
        fluid.promise.resumeSequence(sequencer);
        return sequencer;
    };
 
    fluid.promise.filterNamespaces = function (listeners, namespaces) {
        if (!namespaces) {
            return listeners;
        }
        return fluid.remove_if(fluid.makeArray(listeners), function (element) {
            return element.namespace && !element.softNamespace && !fluid.contains(namespaces, element.namespace);
        });
    };
 
   /** Top-level API to operate a Fluid event which manages a sequence of
     * chained transforms. Rather than being a standard listener accepting the
     * same payload, each listener to the event accepts the payload returned by the
     * previous listener, and returns either a transformed payload or else a promise
     * yielding such a payload.
     * @param {fluid.eventFirer} event - A Fluid event to which the listeners are to be interpreted as
     * elements cooperating in a chained transform. Each listener will receive arguments <code>(payload, options)</code> where <code>payload</code>
     * is the (successful, resolved) return value of the previous listener, and <code>options</code> is the final argument to this function
     * @param {Object|Promise} payload - The initial payload input to the transform chain
     * @param {Object} options - A free object containing options governing the transform. Fields interpreted at this top level are:
     *     reverse {Boolean}: <code>true</code> if the listeners are to be called in reverse order of priority (typically the case for an inverse transform)
     *     filterTransforms {Array}: An array of listener namespaces. If this field is set, only the transform elements whose listener namespaces listed in this array will be applied.
     * @return {fluid.promise} A promise which will yield either the final transformed value, or the response of the first transform which fails.
     */
 
    fluid.promise.fireTransformEvent = function (event, payload, options) {
        options = options || {};
        var listeners = options.reverse ? fluid.makeArray(event.sortedListeners).reverse() :
                fluid.makeArray(event.sortedListeners);
        listeners = fluid.promise.filterNamespaces(listeners, options.filterNamespaces);
        var transformer = fluid.promise.makeTransformer(listeners, payload, options);
        return transformer.promise;
    };
 
 
})(jQuery, fluid_3_0_0);