Retrying
Retry transient domain failures when a new attempt has a useful chance of succeeding. A retry runs the supplied program again from its beginning, including its side effects.
Retrying failed computations
// run: Console.log(loadWithRetries())
error Busy { attempt: number }
function loadWithRetries() {
let attempts = 0
return retry({ times: 3 }) {
attempts += 1
Console.log(`Attempt ${attempts}`)
if (attempts < 3) throw new Busy({ attempt: attempts })
return "Snapshot ready"
}
}Attempt 1
Attempt 2
Attempt 3
Snapshot readyThe initial attempt always runs. { times: 3 } allows three additional attempts, for at most four executions; this example succeeds on its third execution. State declared outside the retry callback, such as attempts, persists across attempts, while local variables inside it start fresh.
Failures stop the current attempt before retry begins. Defects and interruption are not retry signals. Retried writes need an application policy that makes repeated execution safe.
Selecting retryable domain errors
// run: Console.log(Effect.flip(() => sendTransient()).transient)
error RequestRejected {
transient: boolean
}
function sendRequest(): string!RequestRejected! {
throw new RequestRejected({ transient: false })
}
function sendTransient() {
return retry(sendRequest(), {
times: 4,
while: (error) => error.transient
})
}falsewhile permits retry while its predicate holds; until stops retry when its predicate holds. Both can return a boolean or an Effect, so selection can consult a service when needed. Unselected errors remain typed failures rather than becoming successful results.
Controlling timing and limits
// run: Console.log(Effect.flip(() => sendWithBackoff())._tag); Console.log(Effect.flip(() => sendThreeMoreTimes())._tag)
function sendWithBackoff() {
return retry(sendRequest(), {
times: 4,
while: (error) => error.transient,
schedule: Schedule.exponential("150 millis")
})
}
function sendThreeMoreTimes() {
return retry(sendRequest(), Schedule.recurs(3))
}RequestRejected
RequestRejectedA schedule controls delay and can add its own stopping condition. Combining times, predicates, and a schedule stops when the applicable policy says to stop. Schedule.recurs(3) permits three retries; use Effect.repeat for repetition after success.
Recovering after retries end
// run: Console.log(sendOrQueue())
function sendOrQueue() {
return Effect.retryOrElse(
() => sendRequest(),
Schedule.recurs(2),
(error, retries) => {
Console.log(`Queued after ${retries} retries; transient=${error.transient}`)
return "Queued for later"
}
)
}Queued after 2 retries; transient=false
Queued for laterretryOrElse runs recovery after its schedule is exhausted. The callback receives the last error and the schedule's output, which is a retry count for Schedule.recurs. The recovery computation can introduce its own errors and requirements.
Coming from Effect
Both retry(sendRequest(), policy) and Effect.retry(() => sendRequest(), policy) pass the request as a program. The first form passes the call by name because the parameter expects an Effect. Each attempt runs that call again; use a callback or block when an attempt needs several statements.
The matching Effect page: effect.website/docs/v4/error-management/retrying