import { delay } from "@fluidframework/common-utils" ; import { Lumberjack } from "@fluidframework/server-services-telemetry" ; import { NetworkError } from "@fluidframework/server-services-client" ; import { ILogger } from "./lambdas" ; param { ( ) => Promise < T > } api param { string } callName param { number } maxRetries param { number } retryAfterMs param { ILogger } logger param { ( error ) => boolean } shouldIgnoreError param { ( error ) => boolean } shouldRetry param { ( error , numRetries , retryAfterInterval ) => number } calculateIntervalMs param { ( error ) => void } onErrorFn export async function runWithRetry < T > ( api , callName , maxRetries , retryAfterMs , logger ? , shouldIgnoreError ? , shouldRetry ? , calculateIntervalMs = ( error , numRetries , retryAfterInterval ) => retryAfterInterval * 2 ** numRetries , onErrorFn ? ) { let result ; let retryCount = 0 ; let success = false ; do { try { result = await api ( ) ; success = true ; if ( retryCount >= 1 ) { logger ?. info ( `Succeeded in executing ${ callName } with ${ retryCount } retries` ) ; Lumberjack . info ( `Succeeded in executing ${ callName } with ${ retryCount } retries` ) ; } } catch ( error ) { logger ?. error ( `Error running ${ callName }: retryCount ${ retryCount }, error ${ error }` ) ; Lumberjack . error ( `Error running ${ callName }: retryCount ${ retryCount }` , undefined , error ) ; if ( onErrorFn !== undefined ) { onErrorFn ( error ) ; } if ( shouldIgnoreError !== undefined && shouldIgnoreError ( error ) === true ) { logger ?. info ( `Should ignore error for ${ callName }` ) ; Lumberjack . info ( `Should ignore error for ${ callName }` ) ; break ; } else if ( shouldRetry !== undefined && shouldRetry ( error ) === false ) { logger ?. error ( `Should not retry ${ callName } for the current error, rejecting ${ error }` ) ; Lumberjack . error ( `Should not retry ${ callName } for the current error, rejecting` , undefined , error ) ; return Promise . reject ( error ) ; } if ( maxRetries !== - 1 && retryCount >= maxRetries ) { logger ?. error ( `Error after retrying ${ retryCount } times, rejecting` ) ; Lumberjack . error ( `Error after retrying ${ retryCount } times, rejecting` , undefined , error ) ; return Promise . reject ( error ) ; } const intervalMs = calculateIntervalMs ( error , retryCount , retryAfterMs ) ; await delay ( intervalMs ) ; retryCount ++ ; } } while ( ! success ) ; return result ; } summary The difference between  requestWithRetry and  runWithRetry is that  runWithRetry allows the user
 * to decide whether to ignore the error or not, on a "fire and forget" fashion. That makes the return type of
 *  runWithRetry be slightly different, as `undefined` is a possible return value. That is not the case for
 *  requestWithRetry , which focuses on requests/operations where the user always wants the error to be
 * bubbled up, e.g. network requests. It allows for a simpler return type, `T`, since the function would never return
 * anything other than a `T` value - the only other possibility is a promise rejection.
 *  param { ( ) => Promise < T > } request param { string } callName param { ( error ) => boolean } shouldRetry param { number } maxRetries param { number } retryAfterMs param { ( error , numRetries , retryAfterInterval ) => number } calculateIntervalMs param { ( error ) => void } onErrorFn export async function requestWithRetry < T > ( request , callName , shouldRetry = shouldRetryNetworkError , maxRetries = - 1 , retryAfterMs = 1000 , calculateIntervalMs = calculateRetryIntervalForNetworkError , onErrorFn ? ) { let result ; let retryCount = 0 ; let success = false ; do { try { result = await request ( ) ; success = true ; if ( retryCount >= 1 ) { Lumberjack . info ( `Succeeded in executing ${ callName } with ${ retryCount } retries` ) ; } } catch ( error ) { Lumberjack . error ( `Error running ${ callName }: retryCount ${ retryCount }` , undefined , error ) ; if ( onErrorFn !== undefined ) { onErrorFn ( error ) ; } if ( shouldRetry !== undefined && shouldRetry ( error ) === false ) { Lumberjack . error ( `Should not retry ${ callName } for the current error, rejecting` , undefined , error ) ; return Promise . reject ( error ) ; } if ( maxRetries !== - 1 && retryCount >= maxRetries ) { Lumberjack . error ( `Error after retrying ${ retryCount } times, rejecting` , undefined , error ) ; return Promise . reject ( error ) ; } const intervalMs = calculateIntervalMs ( error , retryCount , retryAfterMs ) ; await delay ( intervalMs ) ; retryCount ++ ; } } while ( ! success ) ; return result ; } /**
 * Helper function to decide when or not to retry a  NetworkError .
 * Can be used with  runWithRetry and  requestWithRetry .
 *  param { any } error export function shouldRetryNetworkError ( error ) { if ( error instanceof Error && error ?. name === "NetworkError" ) { const networkError = error as NetworkError ; return ! networkError . isFatal && networkError . canRetry === true ; } return false ; } /**
 * Helper function that calculates interval to wait before retrying. Leverage's  NetworkError . retryAfterMs * if the error is a  NetworkError . Can be used with  runWithRetry and  requestWithRetry .
 *  param { any } error - the error parameter to be inspected. If it is a  NetworkError ,
 *  NetworkError . retryAfterMs will be used as the retry interval.
 *  param { number } numRetries param { number } retryAfterInterval - default value to be used when calculating the retry interval. Used when
 *  NetworkError . retryAfterMs is not defined.
  export function calculateRetryIntervalForNetworkError ( error , numRetries , retryAfterInterval ) { if ( error instanceof Error && error ?. name === "NetworkError" && ( error as NetworkError ) . retryAfterMs ) { return ( error as NetworkError ) . retryAfterMs ; } return retryAfterInterval * 2 ** numRetries ; }