all files / src/src/ Process.js

90.91% Statements 40/44
77.78% Branches 21/27
100% Functions 8/8
90.48% Lines 38/42
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                                                  38×     38×                                               51×   49×   49×     48×         47×       47×         47×       39×         38×   32× 32× 19×         38×                                         51×           50× 50×                             11× 11×                   40× 40×    
/**
 * A Process is a list of steps that are called in sequence. The step can be a number, a
 * promise (jQuery, native, or any other “thenable”), or a function:
 *
 * - **number**: the process will wait for the specified number of milliseconds before proceeding.
 * - **promise**: the process will continue to the next step when the promise is successfully
 *  resolved or stop if the promise is rejected.
 * - **function**: the process will execute the function. The process will stop if the function
 *  returns either a boolean `false` or a promise that is rejected; if the function returns a
 *  number, the process will wait for that number of milliseconds before proceeding.
 *
 * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
 * configured, users can dismiss the error and try the process again, or not. If a process is
 * stopped, its remaining steps will not be performed.
 *
 * @class
 *
 * @constructor
 * @param {number|jQuery.Promise|Function} step Number of milliseconds to wait before proceeding,
 *  promise that must be resolved before proceeding, or a function to execute. See #createStep for
 *  more information. See #createStep for more information.
 * @param {Object} [context=null] Execution context of the function. The context is ignored if the
 *  step is a number or promise.
 */
OO.ui.Process = function ( step, context ) {
	// Properties
	this.steps = [];
 
	// Initialization
	if ( step !== undefined ) {
		this.next( step, context );
	}
};
 
/* Setup */
 
OO.initClass( OO.ui.Process );
 
/* Methods */
 
/**
 * Start the process.
 *
 * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
 *  If any of the steps return a promise that is rejected or a boolean false, this promise is
 *  rejected and any remaining steps are not performed.
 */
OO.ui.Process.prototype.execute = function () {
	/**
	 * Continue execution.
	 *
	 * @ignore
	 * @param {Array} step A function and the context it should be called in
	 * @return {Function} Function that continues the process
	 */
	function proceed( step ) {
		return function () {
			// Execute step in the correct context
			const result = step.callback.call( step.context );
 
			if ( result === false ) {
				// Use rejected promise for boolean false results
				return $.Deferred().reject( [] ).promise();
			}
			if ( typeof result === 'number' ) {
				Iif ( result < 0 ) {
					throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
				}
				// Use a delayed promise for numbers, expecting them to be in milliseconds
				const deferred = $.Deferred();
				setTimeout( deferred.resolve, result );
				return deferred.promise();
			}
			Iif ( result instanceof OO.ui.Error ) {
				// Use rejected promise for error
				return $.Deferred().reject( [ result ] ).promise();
			}
			Iif ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {
				// Use rejected promise for list of errors
				return $.Deferred().reject( result ).promise();
			}
			// Duck-type the object to see if it can produce a promise
			if ( result && typeof result.then === 'function' ) {
				// Use a promise generated from the result
				return $.when( result ).promise();
			}
			// Use resolved promise for other results
			return $.Deferred().resolve().promise();
		};
	}
 
	let promise;
	if ( this.steps.length ) {
		// Generate a chain reaction of promises
		promise = proceed( this.steps[ 0 ] )();
		for ( let i = 1, len = this.steps.length; i < len; i++ ) {
			promise = promise.then( proceed( this.steps[ i ] ) );
		}
	} else {
		promise = $.Deferred().resolve().promise();
	}
 
	return promise;
};
 
/**
 * Create a process step.
 *
 * @private
 * @param {number|jQuery.Promise|Function} step
 *
 * - Number of milliseconds to wait before proceeding
 * - Promise that must be resolved before proceeding
 * - Function to execute
 *   - If the function returns a boolean false the process will stop
 *   - If the function returns a promise, the process will continue to the next
 *     step when the promise is resolved or stop if the promise is rejected
 *   - If the function returns a number, the process will wait for that number of
 *     milliseconds before proceeding
 * @param {Object} [context=null] Execution context of the function. The context is
 *  ignored if the step is a number or promise.
 * @return {Object} Step object, with `callback` and `context` properties
 */
OO.ui.Process.prototype.createStep = function ( step, context ) {
	if ( typeof step === 'number' || typeof step.then === 'function' ) {
		return {
			callback: function () {
				return step;
			},
			context: null
		};
	}
	Eif ( typeof step === 'function' ) {
		return {
			callback: step,
			context: context
		};
	}
	throw new Error( 'Cannot create process step: number, promise or function expected' );
};
 
/**
 * Add step to the beginning of the process.
 *
 * @inheritdoc #createStep
 * @return {OO.ui.Process} this
 * @chainable
 */
OO.ui.Process.prototype.first = function ( step, context ) {
	this.steps.unshift( this.createStep( step, context ) );
	return this;
};
 
/**
 * Add step to the end of the process.
 *
 * @inheritdoc #createStep
 * @return {OO.ui.Process} this
 * @chainable
 */
OO.ui.Process.prototype.next = function ( step, context ) {
	this.steps.push( this.createStep( step, context ) );
	return this;
};