UNPKG

povtor

Version:

Repeat function call depending on the previous call result and specified conditions.

726 lines (658 loc) 22.8 kB
import { retry, ErrorResult, RetryResult, ValueResult } from './index'; /* eslint-disable @typescript-eslint/no-magic-numbers */ describe('retry', () => { let undef; function getAction(value, timeout?: number): () => Promise<number> { return function action(): Promise<number> { return timeout ? new Promise((resolve) => { // eslint-disable-next-line no-param-reassign setTimeout(() => resolve(value++), timeout); }) // eslint-disable-next-line no-param-reassign : Promise.resolve(value++); }; } function getLessTest(max): (value: number) => boolean { return function lessTest(value): boolean { return value < max; }; } it('should call action only once', () => { const num = 3; const result = retry({ action: getAction(num), retryTest: getLessTest(num), retryQty: 200 }); return result.promise.then((value) => { expect( result.attempt ) .toBe( 1 ); expect( value ) .toBe( num ); expect( result.value ) .toBe( value ); expect( result.error ) .toBe( undef ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( 1 ); expect( (result.result[0] as ValueResult).value ) .toBe( value ); expect( result.valueWait ) .toBe( false ); expect( result.wait ) .toBe( false ); }); }); it('should call action specified number of times', () => { const retryQty = 7; const settings = { action: getAction(1), retryTest: getLessTest(1000), retryQty, data: { test: 'value' } }; const result = retry(settings); return result.promise.then((value) => { expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( retryQty + 1 ); expect( result.value ) .toBe( value ); expect( result.error ) .toBe( undef ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( retryQty + 1 ); expect( result.settings ) .toBe( settings ); expect( result.valueWait ) .toBe( false ); expect( result.wait ) .toBe( false ); }); }); it('should call action first time after delay', () => { const num = 10; const delay = 200; const retryQty = 2; const startTime = new Date().getTime(); const result = retry({ action: getAction(num), retryTest: getLessTest(num + 100), retryQty, delay }); return result.promise.then((value) => { expect( new Date().getTime() - startTime ) .toBeGreaterThanOrEqual( delay ); expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( num + retryQty ); expect( result.value ) .toBe( value ); }); }); it('should retry action calls after delay', () => { const num = 4; const delay = 100; const retryTimeout = 200; const retryQty = 2; const startTime = new Date().getTime(); const result = retry({ action: getAction(num), retryTest: getLessTest(num + 100), retryQty, retryTimeout, delay }); return result.promise.then((value) => { expect( new Date().getTime() - startTime ) .toBeGreaterThanOrEqual( delay + (retryQty * retryTimeout) ); expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( num + retryQty ); expect( result.value ) .toBe( value ); }); }); it('should retry action calls after specified timeouts', () => { function timeout(res: RetryResult): number { return (res.settings.secondTimeout as number); } const num = 1; const delay = 100; const retryAttempts = [100, timeout, 300]; const retryQty = retryAttempts.length; const startTime = new Date().getTime(); const result = retry({ action: getAction(num), retryTest: getLessTest(num + 100), retryAttempts, delay, secondTimeout: 200 }); return result.promise.then((value) => { expect( new Date().getTime() - startTime ) .toBeGreaterThanOrEqual( delay + (retryAttempts.reduce( // eslint-disable-next-line multiline-ternary (sum: number, val) => sum + (typeof val === 'function' ? val(result) : val), 0 ) as number) ); expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( num + retryQty ); expect( result.value ) .toBe( value ); }); }); it('should stop repeating of action calls when function from retryAttempts returns false', () => { function retryTimeout(): false { return false; } const start = 10; const result = retry({ action: getAction(start), retryTest: true, retryAttempts: [100, retryTimeout, 200, 300, 400] }); return result.promise.then((value) => { expect( result.attempt ) .toBe( 2 ); expect( value ) .toBe( start + 1 ); expect( result.value ) .toBe( value ); }); }); it('should repeat action calls after timeout returned by function', () => { const timeoutList: number[] = []; function retryTimeout(res: RetryResult): number { const timeout = res.attempt * 100; timeoutList.push(timeout); return timeout; } const retryQty = 3; const result = retry({ action: getAction(0), retryTest: true, retryQty, retryTimeout }); return result.promise.then((value) => { expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( retryQty ); expect( result.value ) .toBe( value ); expect( timeoutList ) .toEqual( [100, 200, 300] ); }); }); it('should stop repeating of action calls when retryTimeout function returns false', () => { const callLimit = 5; function retryTimeout(res: RetryResult): number | false { return res.attempt < res.settings.callLimit ? 100 : false; } const result = retry({ action: getAction(0), retryTest: true, retryTimeout, callLimit }); return result.promise.then((value) => { expect( result.attempt ) .toBe( callLimit ); expect( value ) .toBe( callLimit - 1 ); expect( result.value ) .toBe( value ); }); }); it('should repeat action calls depending on action result', () => { const start = 123; let num = start; const attemptQty = 3; const max = start + attemptQty; const result = retry({ action: () => ++num, retryTest: (value: number, res: RetryResult) => value < res.settings.max, retryQty: -1, retryTimeout: -1, max }); return result.promise.then((value) => { expect( result.attempt ) .toBe( attemptQty ); expect( value ) .toBe( max ); expect( result.value ) .toBe( value ); }); }); it('should call action with context and parameters', () => { const obj = { value: 0, change(getChange: () => number): Promise<number> { return Promise.resolve( this.value += getChange() ); } }; const retryQty = 3; const result = retry({ // eslint-disable-next-line @typescript-eslint/unbound-method action: obj.change, actionContext: obj, actionParams: [ function getChange(): number { return result.attempt * 2; } ], retryTest: getLessTest(1000), retryQty, delay: 0 }); return result.promise.then((value) => { expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( obj.value ); expect( value ) .toBe( 2 + 4 + 6 + 8 ); expect( result.value ) .toBe( value ); }); }); it('should not retry action calls on promise rejection', () => { const reason = 'Test rejection'; const result = retry({ action: () => Promise.reject(reason), retryTest: () => true, retryQty: 100 }); // eslint-disable-next-line dot-notation return result.promise.catch((value) => { expect( result.attempt ) .toBe( 1 ); expect( value ) .toBe( reason ); expect( result.error ) .toBe( value ); expect( result.value ) .toBe( undef ); expect( result.isError ) .toBe( true ); expect( result.result.length ) .toBe( 1 ); expect( (result.result[0] as ErrorResult).error ) .toBe( reason ); expect( result.valueWait ) .toBe( false ); expect( result.wait ) .toBe( false ); }); }); it('should retry action calls on promise rejection', () => { const reason = 'Test rejection'; const retryQty = 2; const result = retry({ action: () => Promise.reject(reason), retryQty, retryOnError: true }); // eslint-disable-next-line dot-notation return result.promise.catch((value) => { expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( reason ); expect( result.error ) .toBe( value ); expect( result.value ) .toBe( undef ); expect( result.isError ) .toBe( true ); expect( result.result.length ) .toBe( retryQty + 1 ); expect( result.valueWait ) .toBe( false ); expect( result.wait ) .toBe( false ); for (const callResult of result.result) { expect( (callResult as ErrorResult).error ) .toBe( reason ); } }); }); it('should retry action calls on promise rejection depending on condition', () => { const maxLen = 3; const msg = '!'; let reason = ''; const result = retry({ action: () => Promise.reject(reason += msg), retryQty: -1, retryOnError: (value: string, res: RetryResult) => value.length < res.settings.maxLen, maxLen }); // eslint-disable-next-line dot-notation return result.promise.catch((value) => { expect( result.attempt ) .toBe( maxLen ); expect( value ) .toBe( reason ); expect( value ) .toBe( msg.repeat(maxLen) ); expect( result.error ) .toBe( value ); expect( result.value ) .toBe( undef ); expect( result.isError ) .toBe( true ); expect( result.result.length ) .toBe( maxLen ); const callResultList = result.result; for (let i = 0, len = callResultList.length; i < len; i++) { expect( (callResultList[i] as ErrorResult).error ) .toBe( msg.repeat(i + 1) ); } }); }); it('should retry action calls on action exception depending on condition', () => { const maxLen = 4; const msg = '!'; let reason = ''; const result = retry({ action: () => { throw new Error(reason += msg); }, retryQty: 500, retryOnError: (err: Error) => err.message.length < maxLen }); // eslint-disable-next-line dot-notation return result.promise.catch((value: Error) => { expect( result.attempt ) .toBe( maxLen ); expect( value ) .toBeInstanceOf( Error ); expect( value.message ) .toBe( reason ); expect( reason ) .toBe( msg.repeat(maxLen) ); expect( result.error ) .toBe( value ); expect( result.value ) .toBe( undef ); expect( result.isError ) .toBe( true ); expect( result.result.length ) .toBe( maxLen ); const callResultList = result.result; for (let i = 0, len = callResultList.length; i < len; i++) { expect( ((callResultList[i] as ErrorResult).error as Error).message ) .toBe( msg.repeat(i + 1) ); } }); }); it('should end action calls because of time limit', () => { const timeLimit = 350; const result = retry({ action: getAction(0), retryTest: true, retryTimeout: 150, timeLimit }); return result.promise.then((value) => { expect( new Date().getTime() - result.startTime ) .toBeGreaterThan( timeLimit ); expect( result.attempt ) .toBe( 4 ); expect( value ) .toBe( 3 ); expect( result.value ) .toBe( value ); expect( result.error ) .toBe( undef ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( result.attempt ); expect( result.valueWait ) .toBe( false ); expect( result.wait ) .toBe( false ); }); }); it('should end action calls on promise rejection because of time limit', () => { const reason = 'No data'; const timeLimit = 250; const result = retry({ action: () => Promise.reject(reason), retryTimeout: 100, retryOnError: true, timeLimit }); // eslint-disable-next-line dot-notation return result.promise.catch((value) => { expect( new Date().getTime() - result.startTime ) .toBeGreaterThan( timeLimit ); expect( result.attempt ) .toBe( 4 ); expect( value ) .toBe( reason ); expect( result.error ) .toBe( value ); expect( result.value ) .toBe( undef ); expect( result.isError ) .toBe( true ); expect( result.result.length ) .toBe( result.attempt ); expect( result.valueWait ) .toBe( false ); expect( result.wait ) .toBe( false ); for (const callResult of result.result) { expect( (callResult as ErrorResult).error ) .toBe( reason ); } }); }); it('result should contain last value and last error', () => { const retryQty = 5; let qty = 0; const result = retry({ action: () => { qty++; return qty % 2 === 1 ? Promise.resolve(qty) : Promise.reject(-qty); }, retryQty, retryTest: true, retryOnError: true }); // eslint-disable-next-line dot-notation return result.promise.catch((value) => { expect( result.attempt ) .toBe( retryQty + 1 ); expect( value ) .toBe( -qty ); expect( qty ) .toBe( retryQty + 1 ); expect( result.value ) .toBe( qty - 1 ); expect( result.error ) .toBe( value ); expect( result.isError ) .toBe( true ); expect( result.result.length ) .toBe( retryQty + 1 ); const callResultList = result.result; for (let i = 0, len = callResultList.length; i < len; i++) { const callResult = callResultList[i]; const val = i + 1; if (val % 2 === 1) { expect( (callResult as ValueResult).value ) .toBe( val ); } else { expect( (callResult as ErrorResult).error ) .toBe( -val ); } } }); }); it('should stop repetition of async action calls', (done) => { const num = 7; const result = retry({ action: getAction(num, 100), retryTest: true, retryQty: -123 }); let qty: number, val: number; expect( result.stopped ) .toBe( false ); setTimeout(() => { qty = result.attempt; val = num + qty - 2; expect( qty ) .toBe( 3 ); expect( result.value ) .toBe( val ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( qty - 1 ); expect( result.stopped ) .toBe( false ); expect( result.wait ) .toBe( false ); expect( result.valueWait ) .toBe( true ); expect( result.stop() ) .toBe( result.promise ); expect( result.value ) .toBe( val ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( qty - 1 ); expect( result.stopped ) .toBe( true ); expect( result.wait ) .toBe( false ); expect( result.valueWait ) .toBe( true ); }, 250); setTimeout(() => { expect( result.attempt ) .toBe( qty ); expect( result.value ) .toBe( val + 1 ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( qty ); expect( result.stopped ) .toBe( true ); expect( result.wait ) .toBe( false ); expect( result.valueWait ) .toBe( false ); // eslint-disable-next-line @typescript-eslint/no-floating-promises result.promise.then((value) => { expect( result.stop() ) .toBe( result.promise ); expect( result.value ) .toBe( value ); expect( value ) .toBe( val + 1 ); done(); }); }, 450); }); it('should stop repetition of action calls', (done) => { const start = 9; let num = start; const result = retry({ action: () => ++num, retryTest: true, retryQty: 400, retryTimeout: 100 }); let qty: number, val; expect( result.stopped ) .toBe( false ); setTimeout(() => { qty = result.attempt; val = start + qty; expect( qty ) .toBe( 3 ); expect( result.value ) .toBe( val ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( qty ); expect( result.stopped ) .toBe( false ); expect( result.wait ) .toBe( true ); expect( result.valueWait ) .toBe( false ); expect( result.stop() ) .toBe( result.promise ); expect( result.value ) .toBe( val ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( qty ); expect( result.stopped ) .toBe( true ); expect( result.wait ) .toBe( false ); expect( result.valueWait ) .toBe( false ); }, 250); setTimeout(() => { expect( result.attempt ) .toBe( qty ); expect( result.value ) .toBe( val ); expect( result.isError ) .toBe( false ); expect( result.result.length ) .toBe( qty ); expect( result.stopped ) .toBe( true ); expect( result.wait ) .toBe( false ); expect( result.valueWait ) .toBe( false ); // eslint-disable-next-line @typescript-eslint/no-floating-promises result.promise.then((value) => { expect( result.stop() ) .toBe( result.promise ); expect( result.value ) .toBe( value ); expect( value ) .toBe( val ); done(); }); }, 450); }); });