export interface RetryStatus { /** Iteration number, where `0` is the first try */ readonly iterNumber : number ; /** Latest attempt's delay. Will always be `undefined` on first run. */ readonly previousDelay : number | undefined ; } export const startStatus = { iterNumber : 0 , previousDelay : undefined } ; /**
 * A `RetryPolicy` is a function that takes an `RetryStatus` and
 * possibly returns a delay in milliseconds. Iteration numbers start
 * at zero and increase by one on each retry. A *undefined* return value from
 * the function implies we have reached the retry limit.
 */ export interface RetryPolicy { ( status ) : number | undefined ; } /**
 * Constant delay with unlimited retries.
 */ export const constantDelay = ( delay ) : RetryPolicy => ( ) => delay ; /**
 * Retry immediately, but only up to `i` times.
 */ export const limitRetries = ( i ) : RetryPolicy => ( status ) => status . iterNumber >= i ? undefined : 0 ; /**
 * Grow delay exponentially each iteration.
 * Each delay will increase by a factor of two.
 */ export const exponentialBackoff = ( delay ) : RetryPolicy => ( status ) => delay * Math . pow ( 2 , status . iterNumber ) ; /**
 * Set a time-upperbound for any delays that may be directed by the
 * given policy.
 */ export const capDelay = ( maxDelay ) => ( policy ) : RetryPolicy => ( status ) => { const delay = policy ( status ) ; return delay === undefined ? undefined : Math . min ( maxDelay , delay ) ; } ; /**
 * Merges two policies. **Quiz**: what does it mean to merge two policies?
 */ export const concat = ( second ) => ( first ) : RetryPolicy => ( status ) => { const delay1 = first ( status ) ; const delay2 = second ( status ) ; if ( delay1 !== undefined && delay2 !== undefined ) { return Math . max ( delay1 , delay2 ) ; } return undefined ; } ; /**
 * Apply policy on status to see what the decision would be.
 */ export const applyPolicy = ( policy ) => ( status ) : RetryStatus => ( { iterNumber : status . iterNumber + 1 , previousDelay : policy ( status ) } ) ; /**
 * Apply a policy keeping all intermediate results.
 */ export const dryRun = ( policy ) : ReadonlyArray < RetryStatus > => { const apply = applyPolicy ( policy ) ; let status = apply ( startStatus ) ; const out = [ status ] ; while ( status . previousDelay !== undefined ) { out . push ( ( status = apply ( out [ out . length - 1 ] ) ) ) ; } return out ; } ; import { pipe } from 'fp-ts/function' ; const myPolicy = pipe ( constantDelay ( 300 ) , concat ( exponentialBackoff ( 200 ) ) , concat ( limitRetries ( 5 ) ) , capDelay ( 2000 ) ) ; console . log ( dryRun ( myPolicy ) ) ;