All files / framework/core/js FluidPromises.js

100% Statements 108/108
97.56% Branches 40/41
100% Functions 30/30
100% Lines 108/108

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                  19x   19x                     19x 822x           822x 966x 804x 654x   150x     966x 870x 36x   834x     966x   822x 781x 2x     779x   779x   822x 42x 2x     40x   40x     822x 819x 819x 819x 122x     822x           19x 851x             19x 6x 4x   2x 2x 2x               19x 234x               19x 4x 4x 4x 3x 3x 1x   2x     1x   4x             19x 406x 1x   405x                     19x 801x 801x   801x     19x 12x 20x 20x 20x   12x     19x 1206x 393x   813x 813x 813x 89x 77x   12x     724x             19x 5x   13x 13x     2x               19x 5x 4x 4x         19x 195x   568x 568x 568x 568x     185x             19x 195x   195x     195x 195x 195x 195x     19x 195x 193x   2x 4x                                     19x 195x 195x   195x 195x 195x          
/*!
 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);