Source: serialized-array-runner.js



"use strict";

/**
 * @file
 * @author Bill Hodges
 */

/**
 * Class for serially executing functions returning promises, with each call
 * passing an element of the array as an argument.
 */
class SerializedArrayRunner {

    /**
     * Create the class and optionaly set the behavior.
     * See <a href="#BehaviorsLink">Behaviors</a> for a list of allowed types.
     * @throws will throw an error if an invalid returnBehavior parameter is passed
     * @param {string} [returnBehavior]  Optional parameter to set the return behavior.
     * The default value is SerializedArrayRunner.LAST_RETURN.
     * @constructor
     *
     */
    constructor (returnBehavior = SerializedArrayRunner.LAST_RETURN) {
        this.behaviorType = returnBehavior;
    }

    // ------------------------------------------------------------------------------
    /**
     *Retrieve the value of the current return behavior
     * @returns {string}
     */
    get behaviorType () {
        return this.returnBehavior;
    }

    // -------------------------------------------------------------------------------


    /**
     * @throws will throw an error if an invalid returnBehavior parameter is passed
     */

    /**
     *  <div id="BehaviorsLink"/>
     *  Set a new runner return behavior. The three types allowed are:
     *  <h5>Behaviors:</h5>
     *  <table>
     *  <thead><tr><th>Behavior Type</th><th>Description</th></tr></thead>
     *  <tr><td>SerializedArrayRunner.LAST_RETURN</td> <td>All but the last resolved data is discarded.</td></tr>
     *  <tr><td>SerializedArrayRunner.ARRAY_RETURN</td> <td>Each resolved data returned is pushed to an array and returned</td></tr>
     *  <tr><td>SerializedArrayRunner.CONCAT_ARRAY_RETURN</td> <td>The resolved data is an array it's contents are concatenated to the
     *  final array for each iteration.</td</tr>
     *  </table>
     *
     * @param {string} returnType
     */


    set behaviorType (returnType) {
        if (!SerializedArrayRunner.VALID_RETURN_BEHAVIOR_TYPES.includes(returnType)) {
            throw new Error('Invalid Return Behavior. Use one of the defined types');
        }
        this.returnBehavior = returnType;
        switch (this.returnBehavior) {
            case SerializedArrayRunner.LAST_RETURN:
                this.is_last_return = true;
                this.is_array_return = false;
                this.is_concat_array_return = false;
                break;
            case SerializedArrayRunner.ARRAY_RETURN:
                this.is_last_return = false;
                this.is_array_return = true;
                this.is_concat_array_return = false;
                break;
            case SerializedArrayRunner.CONCAT_ARRAY_RETURN:
                this.is_last_return = false;
                this.is_array_return = false;
                this.is_concat_array_return = true;
                break;
        }
    }

    // ------------------------------------------------------------------------------
    /**
     * Iterates through the array calling the provided function and waits for the
     * resolution before proceeding. Returns a promise when the iteration is complete.
     * The data resolved depends on the supplied function return data and the configured
     * return behavior
     * @param {Array} arrayToIterate The array who's elements are passed to the function.
     * @param {function} functionToCall The function that gets call. It must return a promise.
     * The first parameter is the current array element.
     * @param {object} scope The scope for the function to be called in. Necessary if the
     * function is class method.
     * @param {...*} args Zero or more arguments to send to the function. Note that
     * the first argument here, is the fourth argument to the called function.
     * @returns {Promise} A resolved promise is returned if all function calls resolve.
     */
    run (arrayToIterate, functionToCall, scope, ...args) {
        return new Promise ( (resolve, reject) => {
            // If we accumulate results (based on the type behavior, this array stores it.
            let resultsArray = [];

            arrayToIterate.reduce(
                (cur, next) => {
                    return cur.then((results) => {
                        // In the first iteration, data is resolve, check for this
                        if (results) {
                            // Do different types of accumulation (or none) based on the desired behavior
                            if (this.is_array_return) {
                                resultsArray.push(results);
                            }
                            else if (this.is_concat_array_return) {
                                if (Array.isArray(results)) {
                                    resultsArray = resultsArray.concat(results);
                                }
                                else {
                                    return reject(`Expected Array, but found type ${typeof results} instead`);
                                }
                            }
                        }
                        return functionToCall.call(scope, next, ...args);
                    });
                },
                Promise.resolve() // initial value
            ).then((results) => {
                // This is our ultimate return, add the results to the array (if needed) and return them.
                if (this.is_array_return) {
                    resultsArray.push(results);
                    resolve(resultsArray);
                }
                else if (this.is_concat_array_return) {
                    if (Array.isArray(results)) {
                        resultsArray = resultsArray.concat(results);
                        resolve(resultsArray);
                    }
                    else {
                        return reject(`Expected Array, but found type ${typeof results} instead`);
                    }
                }
                else {
                    resolve(results);
                }
            }).catch((err) => {
                reject(err);
            });
        });
    }


    /**
     * Used to set the behavior to last return. The data from resolved promises for all
     * but the last call will be discarded.
     * @returns {string}
     */
    static get LAST_RETURN() {
        return 'LAST_RETURN';
    }

    /**
     * Used to set the behavior to array return. The data from resolved promises for all
     * will be pushed into an array and returned with the resolved promise.
     * @return {string}
     */
    static get ARRAY_RETURN() {
        return 'ARRAY_RETURN';
    }

    /**
     * Used to set the behavior to concatenated array return. The data from resolved promises for is
     * expected to be an array. Each array returned is concatenated and returned with the resolved promise.
     * @return {string}
     */
    static get CONCAT_ARRAY_RETURN() {
        return 'CONCAT_ARRAY_RETURN';
    }

    /**
     * Generates an array of valid return types. Used internally for validation of passed parameters.
     * @returns {Array<string>}
     * @private
     */
    static get VALID_RETURN_BEHAVIOR_TYPES () {
        return [
            SerializedArrayRunner.LAST_RETURN,
            SerializedArrayRunner.ARRAY_RETURN,
            SerializedArrayRunner.CONCAT_ARRAY_RETURN
        ];
    }

}

module.exports = SerializedArrayRunner;