timing/Scheduler.js
/**
		 * Attempts an action, repeating if necessary, using an exponential backoff.
		 *
		 * @public
		 * @param {Function} actionToBackoff - The action to attempt. If it fails -- because an error is thrown, a promise is rejected, or the function returns a falsey value -- the action will be invoked again.
		 * @param {number=} millisecondDelay - The amount of time to wait to execute the action. Subsequent failures are multiply this value by 2 ^ [number of failures]. So, a 1000 millisecond backoff would schedule attempts using the following delays: 0, 1000, 2000, 4000, 8000, etc. If not specified, the first attempt will execute immediately, then a value of 1000 will be used.
		 * @param {string=} actionDescription - Description of the action to attempt, used for logging purposes.
		 * @param {number=} maximumAttempts - The number of attempts to before giving up.
		 * @param {Function=} failureCallback - If provided, will be invoked if a function is considered to be failing.
		 * @param {Object=} failureValue - If provided, will consider the result to have failed, if this value is returned (a deep equality check is used). If not provided, an undefined value will trigger a retry.
		 * @returns {Promise}
		 */
Attempts an action, repeating if necessary, using an exponential backoff.
@public
@param {Function} actionToBackoff - The action to attempt. If it fails -- because an error is thrown, a promise is rejected, or the function returns a falsey value -- the action will be invoked again.
@param {number=} millisecondDelay - The amount of time to wait to execute the action. Subsequent failures are multiply this value by 2 ^ [number of failures]. So, a 1000 millisecond backoff would schedule attempts using the following delays: 0, 1000, 2000, 4000, 8000, etc. If not specified, the first attempt will execute immediately, then a value of 1000 will be used.
@param {string=} actionDescription - Description of the action to attempt, used for logging purposes.
@param {number=} maximumAttempts - The number of attempts to before giving up.
@param {Function=} failureCallback - If provided, will be invoked if a function is considered to be failing.
@param {Object=} failureValue - If provided, will consider the result to have failed, if this value is returned (a deep equality check is used). If not provided, an undefined value will trigger a retry.
@returns {Promise}
Attempts an action repeating if necessary using an exponential backoff . @public @param { Function } actionToBackoff - The action to attempt . If it fails -- because an error is thrown a promise is rejected or the function returns a falsey value -- the action will be invoked again . @param { number = } millisecondDelay - The amount of time to wait to execute the action . Subsequent failures are multiply this value by 2 ^ [ number of failures ] . So a 1000 millisecond backoff would schedule attempts using the following delays : 0 1000 2000 4000 8000 etc . If not specified the first attempt will execute immediately then a value of 1000 will be used . @param { string = } actionDescription - Description of the action to attempt used for logging purposes . @param { number = } maximumAttempts - The number of attempts to before giving up . @param { Function = } failureCallback - If provided will be invoked if a function is considered to be failing . @param { Object = } failureValue - If provided will consider the result to have failed if this value is returned ( a deep equality check is used ) . If not provided an undefined value will trigger a retry . @returns { Promise }
backoff(actionToBackoff, millisecondDelay, actionDescription, maximumAttempts, failureCallback, failureValue) {
			return Promise.resolve()
				.then(() => {
					assert.argumentIsRequired(actionToBackoff, 'actionToBackoff', Function);
					assert.argumentIsOptional(millisecondDelay, 'millisecondDelay', Number);
					assert.argumentIsOptional(actionDescription, 'actionDescription', String);
					assert.argumentIsOptional(maximumAttempts, 'maximumAttempts', Number);
					assert.argumentIsOptional(failureCallback, 'failureCallback', Function);
					if (this.getIsDisposed()) {
						throw new Error('The Scheduler has been disposed.');
					}
					const processAction = (attempts) => {
						return Promise.resolve()
							.then(() => {
								let delay;
								if (attempts === 0) {
									delay = 0;
								} else {
									delay = (millisecondDelay || 1000) * Math.pow(2, attempts - 1);
								}
								if (delay === 0) {
									return Promise.resolve()
										.then(() => {
											return actionToBackoff();
										});
								} else {
									return this.schedule(actionToBackoff, delay, `Attempt [ ${attempts} ] for [ ${(actionDescription || 'unnamed action')} ]`);
								}
							}).then((result) => {
								let resultPromise;
								if (!is.undefined(failureValue) && object.equals(result, failureValue)) {
									resultPromise = Promise.reject(`Attempt [ ${attempts} ] for [ ${(actionDescription || 'unnamed action')} ] failed due to invalid result`);
								} else {
									resultPromise = Promise.resolve(result);
								}
								return resultPromise;
							}).catch((e) => {
								if (is.fn(failureCallback)) {
									failureCallback(attempts);
								}
								return Promise.reject(e);
							});
					};
					let attempts = 0;
					const processActionRecursive = () => {
						return processAction(attempts++)
							.catch((e) => {
								if (maximumAttempts > 0 && attempts === maximumAttempts) {
									let message = `Maximum failures reached for ${(actionDescription || 'unnamed action')}`;
									let rejectPromise;
									if (is.object(e)) {
										e.backoff = message;
										rejectPromise = Promise.reject(e);
									} else {
										rejectPromise = Promise.reject(message);
									}
									return rejectPromise;
								} else {
									return processActionRecursive();
								}
							});
					};
					return processActionRecursive();
				});
		}
backoff ( actionToBackoff , millisecondDelay , actionDescription , maximumAttempts , failureCallback , failureValue ) { return Promise . resolve ( ) . then ( ( ) => { assert . argumentIsRequired ( actionToBackoff , 'actionToBackoff' , Function ) ; assert . argumentIsOptional ( millisecondDelay , 'millisecondDelay' , Number ) ; assert . argumentIsOptional ( actionDescription , 'actionDescription' , String ) ; assert . argumentIsOptional ( maximumAttempts , 'maximumAttempts' , Number ) ; assert . argumentIsOptional ( failureCallback , 'failureCallback' , Function ) ; if ( this . getIsDisposed ( ) ) { throw new Error ( 'The Scheduler has been disposed.' ) ; } const processAction = ( attempts ) => { return Promise . resolve ( ) . then ( ( ) => { let delay ; if ( attempts === 0 ) { delay = 0 ; } else { delay = ( millisecondDelay || 1000 ) * Math . pow ( 2 , attempts - 1 ) ; } if ( delay === 0 ) { return Promise . resolve ( ) . then ( ( ) => { return actionToBackoff ( ) ; } ) ; } else { return this . schedule ( actionToBackoff , delay , ` ${ attempts } ${ ( actionDescription || 'unnamed action' ) } ` ) ; } } ) . then ( ( result ) => { let resultPromise ; if ( ! is . undefined ( failureValue ) && object . equals ( result , failureValue ) ) { resultPromise = Promise . reject ( ` ${ attempts } ${ ( actionDescription || 'unnamed action' ) } ` ) ; } else { resultPromise = Promise . resolve ( result ) ; } return resultPromise ; } ) . catch ( ( e ) => { if ( is . fn ( failureCallback ) ) { failureCallback ( attempts ) ; } return Promise . reject ( e ) ; } ) ; } ; let attempts = 0 ; const processActionRecursive = ( ) => { return processAction ( attempts ++ ) . catch ( ( e ) => { if ( maximumAttempts > 0 && attempts === maximumAttempts ) { let message = ` ${ ( actionDescription || 'unnamed action' ) } ` ; let rejectPromise ; if ( is . object ( e ) ) { e . backoff = message ; rejectPromise = Promise . reject ( e ) ; } else { rejectPromise = Promise . reject ( message ) ; } return rejectPromise ; } else { return processActionRecursive ( ) ; } } ) ; } ; return processActionRecursive ( ) ; } ) ; }
Attempts an action, repeating if necessary, using an exponential backoff.
Attempts an action repeating if necessary using an exponential backoff .