Skip to main content

Resilience

The eridu-tech/resilience component provides predefined fault tolerant middlewares.

info

For further information about middlewares refer to eridu-tech/middleware documentation.

Fallback​

The fallback middleware adds fallback value when an error occurs:

Usage​

./samples/fallback-usage.ts
import { fallback } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
fallback({
fallbackValue: 1,
}),
]);

// Will never throw and when error occurs the fallback value will be returned.
console.log(await fn());
info

You can provide synchronous or asynchronous Invocable<[], TValue | Promise<TValue>> as fallback value.

Custom ErrorPolicy​

You can define an ErrorPolicy to specify fallback values for specific error cases:

./samples/fallback-error-policy.ts
import { fallback } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
fallback({
fallbackValue: 1,
// Will only fallback errors that are not a TypeError
errorPolicy: (error) => !(error instanceof TypeError),
}),
]);

await fn();

Callbacks​

You can add callback Invocable that will be called before the fallback value is returned.

./samples/fallback-on-fallback.ts
import { fallback } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
fallback({
fallbackValue: 1,
onFallback: (fallbackData) => console.log(fallbackData),
}),
]);

await fn();
info

For more details about onFallback callback data, see the OnFallbackData type.

Retry​

The retry middleware enables automatic retries for all errors or specific errors, with configurable backoff policies. An error will be thrown when all retry attempts fail.

Usage​

./samples/retry-usage.ts
import { retry } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retry({
// Will retry 4 times
maxAttempts: 4,
}),
]);

await fn();

Custom ErrorPolicy​

You can define an ErrorPolicy to retry specific error cases:

./samples/retry-error-policy.ts
import { retry } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retry({
maxAttempts: 4,
// Will only retry errors that are not TypeError
errorPolicy: (error) => !(error instanceof TypeError),
}),
]);

await fn();

Throw last error​

By default, a RetryResilienceError is thrown when the time window expires. This error aggregates all errors encountered during the retry process. You can instead rethrow the last encountered error:

./samples/retry-throw-last-error.ts
import { retry } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retry({
maxAttempts: 4,
throwLastError: true,
}),
]);

await fn();

Custom BackoffPolicy​

You can use custom BackoffPolicy:

./samples/retry-backoff-policy.ts
import { retry } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retry({
maxAttempts: 4,
// By default a exponential policy is used
backoffPolicy: (attempt: number, _error: unknown) =>
TimeSpan.fromMilliseconds(attempt * 100),
}),
]);

await fn();

Callbacks​

You can add callback Invocable that will be called before execution attempt:

./samples/retry-on-execution-attempt.ts
import { retry } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retry({
maxAttempts: 4,
onExecutionAttempt: (data) => console.log(data),
}),
]);

await fn();

You can add callback Invocable that will be called before the retry delay starts:

info

For more details about onExecutionAttempt callback data, see the OnRetryAttemptData type.

./samples/retry-on-retry-delay.ts
import { retry } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retry({
maxAttempts: 4,
onRetryDelay: (data) => console.log(data),
}),
]);

await fn();
info

For more details about onRetryDelay callback data, see the OnRetryDelayData type.

Retry by interval​

The retryInterval middleware retries a function repeatedly within a given time window, waiting a fixed interval between each attempt. A RetryIntervalResilienceError is thrown when the time window expires and all attempts have failed.

Usage​

./samples/retry-interval-usage.ts
import { retryInterval } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retryInterval({
// Retry for up to 10 seconds
time: TimeSpan.fromSeconds(10),
// Wait 500ms between each attempt
interval: TimeSpan.fromMilliseconds(500),
}),
]);

await fn();

Custom ErrorPolicy​

You can define an ErrorPolicy to retry only specific error cases:

./samples/retry-interval-error-policy.ts
import { retryInterval } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retryInterval({
time: TimeSpan.fromSeconds(10),
interval: TimeSpan.fromMilliseconds(500),
// Will only retry errors that are not a TypeError
errorPolicy: (error) => !(error instanceof TypeError),
}),
]);

await fn();

Throw last error​

By default, a RetryIntervalResilienceError is thrown when the time window expires. This error aggregates all errors encountered during the retry process. You can instead rethrow the last encountered error:

./samples/retry-interval-throw-last-error.ts
import { retryInterval } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retryInterval({
time: TimeSpan.fromSeconds(10),
interval: TimeSpan.fromMilliseconds(500),
throwLastError: true,
}),
]);

await fn();

Callbacks​

You can add callback Invocable that will be called before each execution attempt:

./samples/retry-interval-on-execution-attempt.ts
import { retryInterval } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retryInterval({
time: TimeSpan.fromSeconds(10),
interval: TimeSpan.fromMilliseconds(500),
onExecutionAttempt: (data) => console.log(data),
}),
]);

await fn();
info

For more details about onExecutionAttempt callback data, see the OnRetryAttemptData type.

You can add callback Invocable that will be called before the retry delay starts:

./samples/retry-interval-on-retry-delay.ts
import { retryInterval } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function unstableFn(): Promise<number> {
// We simulate a function that can throw unexpected errors
if (Math.round(Math.random() * 1.5) === 0) {
throw new Error("Unexpected error occurred");
}
return Math.round((Math.random() + 1) * 99);
}
const fn = use(unstableFn, [
retryInterval({
time: TimeSpan.fromSeconds(10),
interval: TimeSpan.fromMilliseconds(500),
onRetryDelay: (data) => console.log(data),
}),
]);

await fn();
info

For more details about onRetryDelay callback data, see the OnRetryDelayData type.

Timeout​

The timeout middleware automatically aborts functions after a specified time period, throwing an error when aborted.

Usage​

./samples/timeout-usage.ts
import { timeout } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function fetchData(): Promise<Response> {
const response = await fetch("ENDPOINT");
console.log("DONE");
return response;
}
const fn = use(fetchData, [
timeout({
waitTime: TimeSpan.fromSeconds(2),
}),
]);

await fn();

Callbacks​

You can add callback Invocable that will be called before the timeout occurs.

./samples/timeout-on-timeout.ts
import { timeout } from "eridu-tech/resilience";
import { use } from "eridu-tech/middleware";
import { TimeSpan } from "eridu-tech/time-span";

async function fetchData(): Promise<Response> {
const response = await fetch("ENDPOINT");
return response;
}
const fn = use(fetchData, [
timeout({
waitTime: TimeSpan.fromSeconds(2),
onTimeout: (data) => console.log(data),
}),
]);

await fn();
info

For more details about onTimeout callback data, see the OnTimeoutData type.

Further information​

For further information refer to eridu-tech/resilience API docs.