UNPKG

forejs

Version:

A lightweight module which provides powerful functionality to organize asynchronous JavaScript code.

1,226 lines (1,077 loc) 36.6 kB
var symbolsSupported = typeof Symbol === "function" && typeof Symbol.iterator === "symbol"; var arrayValuesSupported = typeof Array.prototype.values === "function"; /** * Optimized Array#forEach * @template T * @param {T[]|Arguments} array * @param {function(T, number)} f */ function each(array, f) { var i = -1, length = array.length; while (++i < length) { f(array[i], i); } } /** * Optimized Array#map * @template T * @template S * @param {T[]|Arguments} array * @param {function(T, number): S} f * @return S[] */ function map(array, f) { var i = -1, length = array.length, result = new Array(length); while (++i < length) { result[i] = f(array[i], i); } return result; } /** * Optimized in-place Array#map * @template T * @template S * @param {T[]|Arguments} array * @param {function(T, number): S} f * @return S[] */ function replace(array, f) { var i = -1, length = array.length; while (++i < length) { array[i] = f(array[i], i); } return array; } /** * Optimized Array#every * @template T * @param {T[]|Arguments} array * @param {function(T, number): boolean} predicate * @return boolean */ function every(array, predicate) { var i = -1, length = array.length; while (++i < length) { if (!predicate(array[i], i)) return false; } return true; } /** * Id function * @template T * @param {T} arg * @return {T} */ function id(arg) { return arg; } /** * @callback Callback * @param {*} err * @param {*=} res */ /** * The main entry point. Supports two modes: * <ul> * <li>chain: In chain mode, <code>fore</code> accepts a list of functions that are executed sequentially.</li> * <li>auto: In auto mode, <code>fore</code> accepts an object with identifiers as keys and functions as values. The * identifiers can be referenced by other functions to retrieve its "return value" (see {@link inject} and * {@link fore.ref}). ForeJs now figures out the perfect execution order and runs as much code in parallel as * possible.</li> * </ul> * * The functions passed may have one of the following forms: * <ul> * <li>Node-style asynchronous function: Accepts a number of arbitrary arguments followed by an error-first callback * function. The function must call this callback with either a non-null first argument to signal an error or * with null as first argument followed by any number of "return values". * In chain mode those "return values" are directly passed on to the next function. * In auto mode those values are passed to all dependent functions. If more than one value is passed to the * callback, those values are passed as array to the dependents. Additionally all arguments but the last * (callback) must be eliminated by injections ({@link inject}).</li> * <li>Synchronous function: Sometimes you want to mix synchronous functions into asynchronous code. This is perfectly * ok: Simply put a plain <code>return</code> statement and ignore the callback. If you want to return * <code>undefined</code> on purpose you will need to invoke the callback, however.</li> * <li>Promise: Promises are supported as well. Plain promises must be in a root position.</li> * <li>Promise returning function: A function may as well return a promise instead of invoking the callback. * Unfortunately, the function will still get passed a callback as last argument. If your function cannot cope * with the extra argument, simply wrap it with another function.</li> * <li>Array: As shown in {@link Injector.prototype.args} injections can be syntactically sugared. If you are looking * for a way to iterate over arrays see {@link fore.each}.</li> * <li>Instances of {@link Injector}: An Injector is simply the wrapping type of injected functions.</li> * </ul> * * @example * // chain mode: * fore( * f1, * f2, * ... * ); * // auto mode: * fore({ * a: fa, * b: fb, * c: ["a", "b", fc] // the results of fa and fb are injected into fc once they are ready * }); * @param {(Object.<String, function|Array|Injector|Promise>)} functions For auto mode: an object hash with ids as keys and * functions as values. * @param {...function|Array|Injector|Promise} arguments For chain mode: a list of functions. */ function fore(functions) { if (typeof functions === "object" && Object.getPrototypeOf(functions) === Object.prototype) { dependentExecution(functions); } else { simpleChain(arguments); } } /** * @param {Object.<String, function>} functions */ function dependentExecution(functions) { var ids = Object.getOwnPropertyNames(functions); var valuePipes = {}; each(ids, function (id) { valuePipes[id] = new ValuePipe(); }); var rootInjectors = []; each(ids, function (id) { var hasInjections = [false]; // create nodes var injector = desugar(functions[id]); var combinator = createCombinator(injector); var executor = createExecutor(injector); var valuePipe = valuePipes[id]; // link them if (injector.injections !== null) { replace(injector.injections, function (injection) { return createValueProviderFromInjection(valuePipes, combinator, injection, hasInjections); }); } if (injector.thisInjection !== null) { injector.thisInjection = createValueProviderFromInjection(valuePipes, combinator, injector.thisInjection, hasInjections); } combinator.injector = injector; injector.executor = executor; executor.valuePipe = valuePipe; if (!hasInjections[0]) { rootInjectors.push(injector); } }); // start execution chain each(rootInjectors, function (injector) { injector.execute(true, 1); }); } /** * @param {Object.<String, ValuePipe>} valuePipes * @param {AllCombinationsCombinator} combinator * @param {Injection} injection * @param {boolean[]} hasInjections * @return {ValueProvider} */ function createValueProviderFromInjection(valuePipes, combinator, injection, hasInjections) { var valueProvider = new ValueProvider(); if (injection instanceof Injection) { hasInjections[0] = true; var id = injection.id; var valuePipe; if (id.indexOf("|") >= 0) { // "or injections" ( ref("a|b") ) valuePipe = new DemuxValuePipe(combinator); var ids = id.split("|"); valuePipe.inputPipes = map(ids, function (id) { var inputValuePipe = getValuePipe(valuePipes, id); inputValuePipe.register(valuePipe); return inputValuePipe; }); } else { valuePipe = getValuePipe(valuePipes, id); valuePipe.register(combinator); } combinator.valuePipes.push(valuePipe); combinator.valueProviders.push(valueProvider); } else { valueProvider.value = injection; } return valueProvider; } /** * @param {Object.<String, ValuePipe>} valuePipes * @param {String} id * @return {ValuePipe} */ function getValuePipe(valuePipes, id) { var valuePipe = valuePipes[id]; if (!valuePipe) { throw new Error("Unbound identifier '" + id + "'."); } return valuePipe; } /** * @param {*} functions */ function simpleChain(functions) { var valuePipes = map(functions, function () { return new ValuePipe(); }); var rootInjector = null; each(functions, function (fn, i) { var injector = desugar(fn); injector.isSimpleChain = true; var executor = createExecutor(injector); var valuePipe = valuePipes[i]; injector.injections && replace(injector.injections, function (injection) { var valueProvider = new ValueProvider(); valueProvider.value = injection; return valueProvider; }); if (i > 0) { var combinator = createCombinator(injector); var inputValuePipe = valuePipes[i - 1]; inputValuePipe.register(combinator); var injectionValueProvider; var thisValueProvider = new ValueProvider(); if (injector.thisInjection === void 0) { // inject last result as "this" argument injector.thisInjection = thisValueProvider; injectionValueProvider = thisValueProvider; } else { // inject last result as last argument thisValueProvider.value = injector.thisInjection; injectionValueProvider = new ValueProvider(); if (injector.injections === null) { injector.injections = [injectionValueProvider]; } else { injector.injections.push(injectionValueProvider); } } combinator.valuePipes.push(inputValuePipe); combinator.valueProviders.push(injectionValueProvider); injector.thisInjection = thisValueProvider; combinator.injector = injector; } else { rootInjector = injector; } injector.executor = executor; executor.valuePipe = valuePipe; }); rootInjector.execute(true, 1); } /** * Registers a general error handler. * @callback catch * @param {function(*)} errorHandler A function which accepts one argument - the error. */ /** * Wraps {@link fore} with a try-catch mechanism, which registers a general error handler for all provided functions. * Once any of these functions "returns" an error this error handler will be invoked and the propagation of the * respective execution branch will be stopped. * * This error handler can be shadowed for single functions using {@link Injector.prototype.catch}. * @example * fore.try( * // functions... * ).catch(error => ...) * @return {{catch: catch}} An object with a single function <code>catch</code> to pass the error handler to. */ fore.try = function () { var args = arguments; return { "catch": function (errorHandler) { var functions = args[0]; if (typeof functions === "object" && Object.getPrototypeOf(functions) === Object.prototype) { each(Object.getOwnPropertyNames(functions), function (name) { functions[name] = injectErrorHandler(functions[name], errorHandler); }); fore(functions); } else { fore.apply(null, map(args, function (f) { return injectErrorHandler(f, errorHandler); })); } } } }; /** * Successively returns all values the iterable provides. Values will be processed in parallel. * @see fore.collect * @see fore.reduce * @param {Array|Iterator|Iterable|function|Injector} iterable One of the following: * <ul> * <li>An array of arbitrary values. Use to run a sequence of functions for multiple values.</li> * <li>An Iterator, i.e. an object providing a <code>next</code> function which itself returns objects in the shape of * <code>{value: value&#124;undefined, done: true&#124;false&#124;undefined}</code>.</li> * <li>An Iterable, i.e. an object providing a <code>Symbol.iterator</code> to retrieve an Iterator like above.</li> * <li>A generator function: A function which returns an Iterator (like ES6 <code>function*</code>s do}. If this function * takes arguments make sure to take care of the respective injections.</li> * </ul> * The cases 1 - 3 require <code>fore.each</code> to be in a root position (they don't depend on other functions). * The last case is allowed in any position. * * Any iterable type may also provide promises as values in order to asynchronously generate values. * * Values generated by <code>fore.each</code> will be propagated through subsequent functions causing them to be called * multiple times. If a function has several dependencies (in auto mode) that originate in a <code>fore.each</code>, * it will be invoked with any possible combinations of the incoming values. * @return {Injector} An injector which can be used to inject arguments to the iterable in case it is a generator * function (<code>function*</code>). */ fore.each = function foreEach(iterable) { var injector = (iterable instanceof Injector) ? iterable : new Injector(iterable); injector.mode = ExecutionMode.EACH; return injector; }; /** * The counterpart to {@link fore.each}. Collects all values that were generated by {@link fore.each} and modified by * in-between functions. The results will be passed on to <code>fn</code> as array. If it depends on multiple iterables * <code>fore.collect</code> waits for all branches to finish and each result array will be passed on as separate argument. * * Naturally for asynchronous code the result array will not necessarily have the same order as the input. * @see fore.reduce * @param {function|Injector|Array.<*>} fn * @return {Injector} An injector which can be used to inject arguments to the function. */ fore.collect = function (fn) { var injector = desugar(fn); injector.mode = ExecutionMode.COLLECT; return injector; }; /** * Another counterpart to {@link fore.each}. Behaves much like {@link fore.collect} but provides the results not as array * but in a fashion similar to {@link Array.prototype.reduce}: <code>fn</code> will be called once for each element of * the result. It will receive the accumulator followed by injections followed by a callback * <code>(accumulator, injections, ..., callback)</code>. The "return value" of this call will be the new accumulator * for the next invocation. For the first invocation the accumulation variable is <code>initialValue</code>. * * If there is more than one dependency (and several of these originate in a {@link fore.each}) <code>fn</code> will * be called once for every possible combination of the incoming values. * * Likewise, no specific execution order can be guaranteed. * @example * fore( * fore.each([1, 2, 3, 4]), * plusOne, * fore.reduce((accumulator, value, callback) => callback(null, accumulator * value), 1), * console.log * // result: 1 * 2 * 3 * 4 = 24 * ) * @param {function|Injector|Array.<*>} fn The function which will be invoked with * <code>(accumulator, value, ..., callback)</code> * @param {*} initialValue The value for the accumulator during the first invocation. * @return {Injector} An injector which can be used to inject arguments to the function. */ fore.reduce = function (fn, initialValue) { var injector = desugar(fn); injector.mode = ExecutionMode.REDUCE; if (injector.injections === null) { injector.injections = [initialValue]; } else { injector.injections.unshift(initialValue); } return injector; }; /** * @param {Injector|function} injector * @param {function} errorHandler * @return {Injector} */ function injectErrorHandler(injector, errorHandler) { injector = desugar(injector); if (!injector.errorHandler) { injector.errorHandler = errorHandler; } return injector; } /** * @param {Injector|undefined} injector * @param {*} err */ function handleError(injector, err) { injector.errorHandler(err); } /** * @param {Array.<*>|function|Injector} fn * @param {Injector} fn.injector * @return {Injector} */ function desugar(fn) { if (Array.isArray(fn)) { // desugar ["a", "b", function (a, b) {...}] var functions = []; var injections = []; for (var i = -1, length = fn.length; ++i < length;) { var item = fn[i]; if (typeof item === "function" || item instanceof Injector) { functions.push(item); } else { injections.push(typeof item === "string" ? fore.ref(item) : item); } } var theFunction; if (functions.length === 1) { theFunction = functions[0]; } else { theFunction = function () { var lastIndex = arguments.length - 1; // inject values from "outer" fore into first function of "inner" fore var injector = new Injector(functions[0]); var injections = injector.injections = new Array(lastIndex); for (var j = -1; ++j < lastIndex;) { injections[j] = arguments[j]; } functions[0] = injector; // append "outer" callback as last function var callback = arguments[lastIndex]; functions.push(function (result) { callback(null, result); }); simpleChain(functions); }; } var injector = new Injector(theFunction); injector.injections = injections; return injector; } if (!(fn instanceof Injector)) { return new Injector(fn); } return fn; } /** * References the result of another function when using auto mode. To be used within {@link Injector.prototype.args} or * {@link Injector.prototype.this} * @param {String} id The id to reference. */ fore.ref = function ref(id) { return new Injection(id); }; /** * @constructor * @property {Array.<*>} values * @property {boolean} done */ function ValuePipe() { this.values = []; this.observers = []; this.done = false; this.reachedLast = false; this.expectedLength = 0; this.failedLength = 0; } /** * @param {Combinator|DemuxValuePipe} observer */ ValuePipe.prototype.register = function (observer) { if (this.observers.indexOf(observer) < 0) { this.observers.push(observer); } }; /** * @param {*} value * @param {boolean} done * @param {number} expectedLength */ ValuePipe.prototype.push = function (value, done, expectedLength) { this.values.push(value); this.updateDone(done, expectedLength); var sender = this; each(this.observers, function (observer) { observer.notify(sender); }); }; ValuePipe.prototype.pushFailure = function (done, expectedLength) { this.failedLength++; this.updateDone(done, expectedLength); var sender = this; each(this.observers, function (observer) { observer.notifyFailure(sender); }); }; /** * @param {boolean} done * @param {number} expectedLength * @private */ ValuePipe.prototype.updateDone = function (done, expectedLength) { if (done) { this.reachedLast = true; } this.expectedLength = Math.max(this.expectedLength, expectedLength); this.done = this.reachedLast && this.values.length === this.expectedLength - this.failedLength; }; /** * This is neither a real {@link ValuePipe} nor a real {@link Combinator}. It de-multiplexes several ValuePipes to one * Combinator input. * @param {Combinator} combinator * @constructor */ function DemuxValuePipe(combinator) { this.values = []; this.combinator = combinator; this.inputPipes = null; this.done = false; } DemuxValuePipe.prototype.notify = function (sender) { var senderValues = sender.values; this.values.push(senderValues[senderValues.length - 1]); this.updateDone(); this.combinator.notify(this); }; DemuxValuePipe.prototype.notifyFailure = function (sender) { this.updateDone(); this.combinator.notifyFailure(this); }; DemuxValuePipe.prototype.updateDone = function () { this.done = every(this.inputPipes, function (valuePipe) { return valuePipe.done }); }; /** * @constructor * @abstract * @property {Injector} injector * @property {ValuePipe[]} valuePipes * @property {ValueProvider[]} valueProviders */ function Combinator() { this.injector = null; this.valuePipes = []; this.valueProviders = []; } /** * @param {ValuePipe|DemuxValuePipe} sender * @abstract */ Combinator.prototype.notify = function (sender) { }; /** * @param {ValuePipe|DemuxValuePipe} sender */ Combinator.prototype.notifyFailure = function (sender) { }; /** * @constructor * @extends Combinator */ function SimpleCombinator() { Combinator.call(this); } SimpleCombinator.prototype = Object.create(Combinator.prototype); SimpleCombinator.prototype.notify = function (sender) { var valuePipe = this.valuePipes[0]; var values = valuePipe.values; this.valueProviders[0].value = values[values.length - 1]; this.injector.execute(valuePipe.done, values.length); }; /** * @constructor * @extends Combinator */ function AllCombinationsCombinator() { Combinator.call(this); this.executionCounter = 0; } AllCombinationsCombinator.prototype = Object.create(Combinator.prototype); AllCombinationsCombinator.prototype.notify = function (sender) { var valuePipes = this.valuePipes; var valueProviders = this.valueProviders; var pipesDone = true; var possibleCombinations = 1; var senderIndex = -1; var currentLengths = map(valuePipes, function (valuePipe, i) { pipesDone &= valuePipe.done; var length = valuePipe.values.length; possibleCombinations *= length; if (valuePipe === sender) { senderIndex = i; } return length; }); if (this.executionCounter >= possibleCombinations) { // prevent double execution return; } this.executionCounter = possibleCombinations; valueProviders[senderIndex].value = valuePipes[senderIndex].values[currentLengths[senderIndex] - 1]; var currentIndices = replace(new Array(valuePipes.length), function () { return 0; }); var carry = false; while (!carry) { carry = true; // set current values and count one step var i = -1, length = valuePipes.length; while (++i < length) { if (i === senderIndex) { continue; } valueProviders[i].value = valuePipes[i].values[currentIndices[i]]; if (carry) { currentIndices[i]++; carry = false; if (currentIndices[i] === currentLengths[i]) { currentIndices[i] = 0; carry = true; } } } // emit value combination this.injector.execute(pipesDone && carry, possibleCombinations); } }; /** * @constructor * @extends Combinator */ function CollectorCombinator() { Combinator.call(this); } CollectorCombinator.prototype = Object.create(Combinator.prototype); CollectorCombinator.prototype.notify = CollectorCombinator.prototype.notifyFailure = function (sender) { var valuePipes = this.valuePipes; if (!every(valuePipes, function (pipe) { return pipe.done })) { return; } var valueProviders = this.valueProviders; each(valuePipes, function (valuePipe, i) { valueProviders[i].value = map(valuePipe.values, id); }); this.injector.execute(true, 1); }; /** * @param {Injector} injector * @return {*} */ function createCombinator(injector) { if (injector.mode === ExecutionMode.COLLECT) { return new CollectorCombinator(); } else if (injector.injections === null || (injector.injections.length <= 1 && injector.thisInjection === null) || (injector.thisInjection !== null && (injector.injections === null || injector.injections.length === 0))) { return new SimpleCombinator(); } return new AllCombinationsCombinator(); } /** * @constructor * @property {*} value */ function ValueProvider() { this.value = null; } /** * @param {String} id * @constructor * @property {String} id */ function Injection(id) { this.id = id; } /** * @readonly * @enum {number} */ var ExecutionMode = { STANDARD: 0, EACH: 1, COLLECT: 2, REDUCE: 3 }; /** * Provides methods to inject dependencies or constant values into functions. This means that a function will be * "partially evaluated". Additional arguments like the callback function or, in simple mode, the "return values" of the * previous function are provided by {@link fore}. * Retrieved by {@link inject}. Don't call this constructor by yourself. * @constructor */ function Injector(fn) { this.fn = fn; this.injections = null; this.thisInjection = null; this.errorHandler = null; this.mode = ExecutionMode.STANDARD; this.isSimpleChain = false; this.executor = null; } /** * Injects constant values or dependencies into this function starting from the left. Use {@link fore.ref} to inject * dependencies in auto mode. If no string constants need to be injected, <code>inject.args(...)</code> can also be * written using a shorter array notation, which is especially handy for anonymous functions: * @example * ((arg1, arg2) => ...).inject.args(fore.ref("arg1"), fore.ref("arg2")) * // shorter: * ["arg1", "arg2", (arg1, arg2) => ...] * @param {Injection|*} arguments The list of injections. * @return {Injector} <code>this</code>. * @chainable */ Injector.prototype.args = function args() { this.injections = map(arguments, function (arg) { return arg; }); return this; }; /** * Injects a constant value or dependency as <code>this</code> argument. Use {@link fore.ref} to inject dependencies in * auto mode. In chain mode, call this function without arguments in order to retrieve the "return value" of the previous * function as <code>this</code> argument instead of as first argument. If the previous function "returns" multiple values * only the first one will be passed, the others are ignored. * @param {Injection|Object} object The injection. * @return {Injector} <code>this</code> * @chainable */ Injector.prototype.this = function ths(object) { this.thisInjection = object; return this; }; /** * Attaches an error handler to this function that will be called if the function invokes its callback with a non-null * first argument. This error will be passed as first argument to the errorHandler. Once an error occurs, the propagation * of this execution branch will be stopped. * * It is also possible to register a general error handler to the entire fore-block using {@link fore.try}. Error handlers * attached directly to the function are prioritized. * * If an error occurs and no error handler has been registered the execution will break. So catch your errors! * @param {function(*)} errorHandler The error handler, a function which accepts one argument. * @return {Injector} <code>this</code> * @chainable */ Injector.prototype.catch = function ctch(errorHandler) { this.errorHandler = errorHandler; return this; }; /** * @param {boolean} done * @param {number} expectedLength * @protected */ Injector.prototype.execute = function (done, expectedLength) { var injections = this.injections; var args; if (this.isSimpleChain) { args = []; if (injections !== null) { each(injections, function (valueProvider) { var value = valueProvider.value; if (value instanceof ArgumentsWrapper) { Array.prototype.push.apply(args, value.args); } else { args.push(value); } }); } } else { args = injections === null ? [] : map(injections, function (valueProvider) { var value = valueProvider.value; return (value instanceof ArgumentsWrapper) ? value.args : value; }); } var thisArg; var thisInjection = this.thisInjection; if (thisInjection instanceof ValueProvider) { if (thisInjection.value instanceof ArgumentsWrapper) { thisArg = thisInjection.value.args[0]; } else { thisArg = thisInjection.value; } } this.executor.execute(thisArg, args, done, expectedLength); }; /** * @param {Arguments} args * @constructor */ function ArgumentsWrapper(args) { var clone = this.args = new Array(args.length - 1); var i = 0, length = args.length; while (++i < length) { clone[i - 1] = args[i]; } } /** * @constructor * @abstract * @property {ValuePipe} valuePipe * @property {Injector} injector */ function Executor(injector) { this.injector = injector; this.valuePipe = null; } /** * @abstract * @param {Object|null} thisArg * @param {Array.<*>} args * @param {boolean} done * @param {number} expectedLength */ Executor.prototype.execute = function (thisArg, args, done, expectedLength) { }; /** * @param {Injector} injector * @constructor * @extends Executor */ function AsyncExecutor(injector) { Executor.call(this, injector); this.fn = injector.fn; } AsyncExecutor.prototype = Object.create(Executor.prototype); AsyncExecutor.prototype.execute = function (thisArg, args, done, expectedLength) { var valuePipe = this.valuePipe; function emit(res) { valuePipe.push(res, done, expectedLength); } executeFunction(this.fn, thisArg, args, this.injector, emit); }; /** * @param {function} fn * @param {*} thisArg * @param {Array.<*>} args * @param {Injector} injector * @param {function(*)} emit */ function executeFunction(fn, thisArg, args, injector, emit) { function callback(err, res) { if (err !== null) { handleError(injector, err); } else { if (arguments.length > 2) { res = new ArgumentsWrapper(arguments); } emit(res); } } args.push(callback); var returnValue = fn.apply(thisArg, args); if (returnValue === void 0) { return; } if (returnValue instanceof Promise) { executePromise(returnValue, injector, emit); } else { emit(returnValue); } } /** * @param {Injector} injector * @param {Promise} injector.fn * @constructor * @extends Executor */ function PromiseExecutor(injector) { Executor.call(this, injector); this.promise = injector.fn; } PromiseExecutor.prototype = Object.create(Executor.prototype); PromiseExecutor.prototype.execute = function (thisArg, args, done, expectedLength) { var valuePipe = this.valuePipe; executePromise(this.promise, this.injector, function (value) { valuePipe.push(value, true, 1); }); }; /** * @param {Promise} promise * @param {Injector} injector * @param {function(*)} emit */ function executePromise(promise, injector, emit) { promise .then(emit) .catch(function (err) { handleError(injector, err); }); } /** * @param {Iterator} iterator * @param {Injector} injector * @constructor * @extends Executor */ function IteratorExecutor(iterator, injector) { Executor.call(this, injector); this.iterator = iterator; } IteratorExecutor.prototype = Object.create(Executor.prototype); IteratorExecutor.prototype.execute = function (thisArg, args, done, expectedLength) { var iterator = this.iterator; var valuePipe = this.valuePipe; var injector = this.injector; for (var next = iterator.next(), length = 1; !next.done; length++) { var value = next.value; // retrieve next already here because we must know if the iterator is done next = iterator.next(); if (value instanceof Promise) { value .then(function (done, expectedLength, value) { valuePipe.push(value, done, expectedLength) }.bind(null, next.done, length)) .catch(function (done, expectedLength, err) { handleError(injector, err); valuePipe.pushFailure(done, expectedLength); }.bind(null, next.done, length)); } else { valuePipe.push(value, next.done, length); } } }; /** * @param {function} generator * @param {Injector} injector * @constructor * @extends Executor */ function GeneratorExecutor(generator, injector) { Executor.call(this, injector); this.generator = generator; } GeneratorExecutor.prototype = Object.create(Executor.prototype); GeneratorExecutor.prototype.execute = function (thisArg, args, done, expectedLength) { var iterator = this.generator.apply(thisArg, args); var iteratorExecutor = new IteratorExecutor(iterator, this.injector); iteratorExecutor.valuePipe = this.valuePipe; iteratorExecutor.execute(thisArg, args, done, expectedLength); }; /** * @param {Injector} injector * @constructor * @extends Executor */ function ReduceExecutor(injector) { Executor.call(this, injector); this.fn = injector.fn; this.accumulationValue = injector.injections[0]; this.pendingExecutions = []; this.isRunnning = false; this.executedLength = 0; this.done = false; } ReduceExecutor.prototype = Object.create(Executor.prototype); ReduceExecutor.prototype.execute = function (thisArg, args, done, expectedLength) { var pendingExecutions = this.pendingExecutions; // save snapshot pendingExecutions.push([thisArg, args]); this.done = this.done || done; var execute = function () { if (pendingExecutions.length === 0) { return; } // restore snapshot var parameters = pendingExecutions.pop(); parameters[1][0] = this.accumulationValue; this.executedLength++; executeFunction(this.fn, parameters[0], parameters[1], this.injector, function (value) { if (this.done && pendingExecutions.length === 0) { this.valuePipe.push(value, true, 1); } else { this.accumulationValue = value; execute(); } }.bind(this)); }.bind(this); if (!this.isRunnning) { this.isRunnning = true; execute(); this.isRunnning = false; } }; /** * @param {Injector} injector * @return {Executor} */ function createExecutor(injector) { switch (injector.mode) { case ExecutionMode.STANDARD: case ExecutionMode.COLLECT: if (injector.fn instanceof Promise) { return new PromiseExecutor(injector); } else { return new AsyncExecutor(injector); } case ExecutionMode.REDUCE: return new ReduceExecutor(injector); case ExecutionMode.EACH: var iterable = injector.fn; if (Array.isArray(iterable)) { var iterator; if (arrayValuesSupported) { iterator = iterable.values(); } else { iterator = { next: (function () { var array = iterable; var i = 0; return function () { return i < array.length ? {value: array[i++]} : {done: true}; } })() } } return new IteratorExecutor(iterator, injector) } if (typeof iterable === "function") { return new GeneratorExecutor(iterable, injector); } if (symbolsSupported && typeof iterable === "object" && typeof iterable[Symbol.iterator] === "function") { return new IteratorExecutor(iterable[Symbol.iterator](), injector); } } } /** * Starts the injection of values or dependencies into this function. Should be followed by one of the {@link Injector} * methods. Use <code>inject</code> to avoid function wrappers or things like {@link Function.prototype.bind}. * @return {Injector} The injector. */ function inject() { return new Injector(this); } /** * @param {function} fn * @return {Injector} */ function foreInject(fn) { return inject.call(fn); } // add inject to Function prototype function attachInject() { Object.defineProperty(Function.prototype, "inject", { get: inject, configurable: true, enumerable: false }); } /** * Configures foreJs. * @param {object=} properties The configuration object. * @param {boolean=} properties.dontHackFunctionPrototype Set <code>true</code> to keep <code>Function.prototype</code> * clean and omit the {@link inject} getter. {@link inject} now exists as static property of {@link fore} instead: * <code>fore.inject(myFunction).args(...)</code>. Default: <code>false</code> */ fore.config = function (properties) { var config = new Config(properties); if (config.dontHackFunctionPrototype) { if (Object.getOwnPropertyDescriptor(Function.prototype, "inject").get === inject) { delete Function.prototype.inject; } fore.inject = foreInject; } else { if (typeof fore.inject === "function") { delete fore.inject; } attachInject(); } }; /** * @param {Object} config * @constructor * @property {boolean} dontHackFunctionPrototype */ function Config(config) { config && each(Object.getOwnPropertyNames(config), function (name) { this[name] = config[name]; }.bind(this)); } Config.prototype.dontHackFunctionPrototype = false; fore.config(); module.exports = fore;