lib/index.js
async function retry(func, options) {
	if (!options.timeoutMs && !options.retry)
		throw new Error('Invalid argument: either options.timeoutMs or options.retry must be specified')
	if (options.timeoutMs < 0)
		throw new Error('Invalid argument: options.timeoutMs < 0')
	if (options.retry < 0)
		throw new Error('Invalid argument: options.retry < 0')
	if (options.intervalMs < 0)
		throw new Error('Invalid argument: options.intervalMs < 0')
	if (!options.intervalMs && options.timeoutMs) {
		options.intervalMs = options.timeoutMs / 60
		if (options.intervalMs < 10000)
			options.intervalMs = 10000
	}

	let filter = options.filter || ((err, ret, _hasError) => _hasError)

	//start of backward compatibility
	if (options.filterReject || options.filterResolve) {
		filter = (err, ret, _hasError) => {
			if (_hasError)
				return options.filterReject && options.filterReject(err)
			return options.filterResolve && options.filterResolve(ret)
		}
	}
	//end of backward compatibility

	let start = Date.now()

	let log = options.log || (()=>0)
	let name = options.name || '<no name>'
	let n = 0
	let checkRetry = async (state) => {
		if (options.retry && ++n > options.retry) {
			let msg = `RetryTask [${name}] - State=${state}. FAILED: Retry limit reached.`
			log(msg)
			throw new Error(msg)
		}
		let now = Date.now()
		if (options.timeoutMs && now - start > options.timeoutMs) {
			let msg = `RetryTask [${name}] - State=${state}. FAILED: Retry Timeout.`
			log(msg)
			throw new Error(msg)
		}

		let msg = ''
		if (options.retry) {
			msg = `${n}/${options.retry}`
		}
		if (options.timeoutMs) {
			let percent = (now - start) / options.timeoutMs * 100 | 0
			if (options.retry)
				msg += ', '
			msg += `timeout ${percent}%`
		}
		log(`RetryTask [${name}] - State=${state}. Retry=${msg}...`)
		await delay(options.intervalMs)
	}

	let ret
	let err
	let _hasError
	for (;;) {
		ret = undefined
		err = undefined
		_hasError = undefined
		try {
			ret = await func()
		} catch (e) {
			err = e
			_hasError = true
		}

		if (await filter(err, ret, _hasError)) {
			await checkRetry(err || ret)
			continue
		}

		if (err)
			throw err
		return ret
	}
}
async function retry(func, options) {
	if (!options.timeoutMs && !options.retry)
		throw new Error('Invalid argument: either options.timeoutMs or options.retry must be specified')
	if (options.timeoutMs < 0)
		throw new Error('Invalid argument: options.timeoutMs < 0')
	if (options.retry < 0)
		throw new Error('Invalid argument: options.retry < 0')
	if (options.intervalMs < 0)
		throw new Error('Invalid argument: options.intervalMs < 0')
	if (!options.intervalMs && options.timeoutMs) {
		options.intervalMs = options.timeoutMs / 60
		if (options.intervalMs < 10000)
			options.intervalMs = 10000
	}

	let filter = options.filter || ((err, ret, _hasError) => _hasError)

	//start of backward compatibility
	if (options.filterReject || options.filterResolve) {
		filter = (err, ret, _hasError) => {
			if (_hasError)
				return options.filterReject && options.filterReject(err)
			return options.filterResolve && options.filterResolve(ret)
		}
	}
	//end of backward compatibility

	let start = Date.now()

	let log = options.log || (()=>0)
	let name = options.name || '<no name>'
	let n = 0
	let checkRetry = async (state) => {
		if (options.retry && ++n > options.retry) {
			let msg = `RetryTask [${name}] - State=${state}. FAILED: Retry limit reached.`
			log(msg)
			throw new Error(msg)
		}
		let now = Date.now()
		if (options.timeoutMs && now - start > options.timeoutMs) {
			let msg = `RetryTask [${name}] - State=${state}. FAILED: Retry Timeout.`
			log(msg)
			throw new Error(msg)
		}

		let msg = ''
		if (options.retry) {
			msg = `${n}/${options.retry}`
		}
		if (options.timeoutMs) {
			let percent = (now - start) / options.timeoutMs * 100 | 0
			if (options.retry)
				msg += ', '
			msg += `timeout ${percent}%`
		}
		log(`RetryTask [${name}] - State=${state}. Retry=${msg}...`)
		await delay(options.intervalMs)
	}

	let ret
	let err
	let _hasError
	for (;;) {
		ret = undefined
		err = undefined
		_hasError = undefined
		try {
			ret = await func()
		} catch (e) {
			err = e
			_hasError = true
		}

		if (await filter(err, ret, _hasError)) {
			await checkRetry(err || ret)
			continue
		}

		if (err)
			throw err
		return ret
	}
}
async function retry ( func , options ) { if ( ! options . timeoutMs && ! options . retry ) throw new Error ( 'Invalid argument: either options.timeoutMs or options.retry must be specified' ) if ( options . timeoutMs < 0 ) throw new Error ( 'Invalid argument: options.timeoutMs < 0' ) if ( options . retry < 0 ) throw new Error ( 'Invalid argument: options.retry < 0' ) if ( options . intervalMs < 0 ) throw new Error ( 'Invalid argument: options.intervalMs < 0' ) if ( ! options . intervalMs && options . timeoutMs ) { options . intervalMs = options . timeoutMs / 60 if ( options . intervalMs < 10000 ) options . intervalMs = 10000 } let filter = options . filter || ( ( err , ret , _hasError ) => _hasError ) //start of backward compatibility if ( options . filterReject || options . filterResolve ) { filter = ( err , ret , _hasError ) => { if ( _hasError ) return options . filterReject && options . filterReject ( err ) return options . filterResolve && options . filterResolve ( ret ) } } //end of backward compatibility let start = Date . now ( ) let log = options . log || ( ( ) => 0 ) let name = options . name || '<no name>' let n = 0 let checkRetry = async ( state ) => { if ( options . retry && ++ n > options . retry ) { let msg = ` ${ name } ${ state } ` log ( msg ) throw new Error ( msg ) } let now = Date . now ( ) if ( options . timeoutMs && now - start > options . timeoutMs ) { let msg = ` ${ name } ${ state } ` log ( msg ) throw new Error ( msg ) } let msg = '' if ( options . retry ) { msg = ` ${ n } ${ options . retry } ` } if ( options . timeoutMs ) { let percent = ( now - start ) / options . timeoutMs * 100 | 0 if ( options . retry ) msg += ', ' msg += ` ${ percent } ` } log ( ` ${ name } ${ state } ${ msg } ` ) await delay ( options . intervalMs ) } let ret let err let _hasError for ( ; ; ) { ret = undefined err = undefined _hasError = undefined try { ret = await func ( ) } catch ( e ) { err = e _hasError = true } if ( await filter ( err , ret , _hasError ) ) { await checkRetry ( err || ret ) continue } if ( err ) throw err return ret } }
Retry the specific task conditionally

@param    {function}	func
@param    {object}	options
@property {function}	options.filter		- A callback filter to control retry, based on result or error.
Retry on true return. The default filter is: (err, ret)=>err
@property {number}	options.retry		- max retry attempt. 0 indicates no limit
@property {number}	options.timeoutMs	- total timeout limit. 0 indicates no total timeout
@property {number}	options.intervalMs	- wait before retry
@property {function}	[options.log]		- optionally, log the details
@property {string}	[options.name]		- name shown in log

@return {Promise}
Retry the specific task conditionally