Source: executor/abstractPromiseExecutor.js

/**
 * @author Sloan Seaman 
 * @copyright 2016 and on
 * @version .1
 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */

/** @private */
var svUtil = require('../util.js');
var log = require('winston-simple').getLogger('PromiseExecutor');

/**
 * The AbstractPromiseExceutor will execute a list of internal items taking into consideration async processing
 * in each item and awaiting for their completion via the `Promise`s they return
 * 
 * @constructor
 */
function AbstractPromiseExecutor() {}

/**
 * Start the execution
 * 
 * @function
 * @abstract
 * @param  {SVContext} svContext The SVContext to use for execution
 */
AbstractPromiseExecutor.prototype.execute = function(svContext) {
	/*eslint no-unused-vars: ["error", { "args": "none" }]*/
	throw new Error('Must be implemented by subclass');
};

/**
 * Method that actually does the execution of the items.
 *
 * If the item in the list does not return a `Promise`, this treats the call as sync and goes to the next item.
 * If the item in the list uses the deprecated `callback.success()` or `callback.failure()`, it treats the call
 * as sync as well.
 * Only a `Promise` is treated as async and everything waits until its completion
 * 
 * @param  {String} method    The function to execute on the item
 * @param  {Object} items     The items to iterate through
 * @param  {SVContext} svContext The SVContex to use when calling the methods.
 * @return {Promise}          The Promise that will have its `then` called when everything has completed
 */
AbstractPromiseExecutor.prototype._doExecute = function(method, items, svContext) {
	return new Promise(function(topResolve, topReject) {
		// these control the loop for each item loop (related to method)
		var itemLength = items.length;
		var idx = 0;

		/**
		 * Function that actually can handle the looping through resolve and rejects
		 * 
		 * @anonymous
		 * @param {Function} rootOut The main promises function to call when everything is done and we want to report as such
		 * @param {Function} parentResolve The resolve method to call when we want to go to the next successful (resolve)
		 *        iteration of the loop
		 * @param {Function} parentReject The reject method to call when we want to go to the next unsuccessful (reject)
		 *        iteration of the loop
		 * @param {Function} pathToFollow The method (resolve or reject) that represents the current path we are executing down
		 *        and should be called next
		 */
		var promiseHandler = function(rootOut, parentResolve, parentReject, pathToFollow) {
			if (idx < itemLength) {
				if (svUtil.isFunction(items[idx][method])) {
					log.info('Executing '+method+' for '+items[idx].getName());

					try {
						// execute the filter
						var exeResult = items[idx++][method](svContext.lambda.event, svContext.lambda.context, svContext);
						// result wasn't a promise,  so it wasn't async and we should just go to the next one
						if (!exeResult || (!(exeResult && exeResult.then))) {
							pathToFollow(); 
						}
						// It was a promise, go to the next loop iteration when done (by calling back to the correct path)
						else {
							exeResult.then(parentResolve, parentReject); // note that these are reference to function and not execution (no ())
						}
					}
					catch (e) {
						var oldIdx = idx - 1;
						// it's using old code (success / failure) and should still be handled
						if ((e.message === "Cannot read property 'success' of undefined") ||
							(e.message === "Cannot read property 'failure' of undefined")) 
						{
							log.warn(items[oldIdx].getName()+" is using legacy callback method 'success'");
							// it's actually good, just old code, so keep going
							pathToFollow();
						}
						// Something went wrong
						else {
							log.error('Error executing '+items[oldIdx].getName()+'. '+e.message);
						}
					}
				}
				// don't have the function signature we are looking for, skip
				else {
					idx ++;
					pathToFollow();
				}
			}
			// out of things to do, call the rootOut to tell the main promise we are done
			else { 
				rootOut();
			}
		};

		// Holds the resolve and reject promises that can be called for each iteration of the loop.
		// Needs to be a variable so it can be references multiple times
		var promiseFunctions = {
			resolve () {
				promiseHandler(topResolve, promiseFunctions.resolve, promiseFunctions.reject, promiseFunctions.resolve);
			},
			reject : function() {
				promiseHandler(topReject, promiseFunctions.resolve, promiseFunctions.reject, promiseFunctions.reject);
			}
		};

		// start things off
		promiseFunctions.resolve();
	});
};

module.exports = AbstractPromiseExecutor;