-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Retry backoff example #881
base: v3
Are you sure you want to change the base?
Conversation
I'd omit the race since it can just be part of the flow of the operation: export function* retryBackoffExample<T>(
op: () => Operation<T>,
{ attempts = 5, startDelay = 5, maxDelay = 200 } = {},
): Operation<T> {
yield* spawn(function* () ) {
yield* sleep(maxDelay);
throw new Error('timeout');
});
let currentDelay = startDelay;
let lastError: Error;
for (let attempt = 0; attempt < attempts; attempts++) {
try {
return yield* op();
} catch (e) {
if (!isKnownError(e)) {
throw e;
} else {
lastError = e;
yield* sleep(currentDelay);
currentDelay = currentDelay * 2;
}
}
throw lastError;
} |
I initially wrote it this way but it didn't match the requirements
|
Ah, ok. I misunderstood then. There is no timeout on the operation as a whole, but just a cap on the delay to wait between operations. In that case, I think it can be expressed even more succinctly with a single loop: import { Operation, sleep } from "effection";
export function* retryBackoffExample<T>(
op: () => Operation<T>,
{ attempts = 5, startDelay = 5, maxDelay = 200 } = {},
): Operation<T> {
let current = { attempt: 0, delay: startDelay, error: new Error() };
while (current.attempt < attempts) {
try {
return yield* op();
} catch (error) {
if (!isKnownError(error)) {
throw error;
} else {
yield* sleep(current.delay);
current.attempt++;
current.error = error;
current.delay = Math.min(current.delay * 2, maxDelay);
}
}
}
throw current.error;
} If we wanted to, we could easily imagine writing a |
What about this? shorter and no tears. import { call, type Operation, race, sleep } from "effection";
export function* retryBackoffExample<T>(
op: () => Operation<T>,
{ maxAttempts = 5, startDelay = 5, maxDelay = 200 } = {},
): Operation<T> {
return race([
timeout(maxDelay),
call(function* () {
let attempt = 0;
for (let delay = startDelay;; delay = delay * 2) {
try {
return yield* op();
} catch (error) {
if (!isKnownError(error) || attempt++ >= attempts) {
throw error;
}
yield* sleep(delay);
}
}
}),
]);
}
function* timeout(afterMillis: number): Operation<never> {
yield* sleep(afterMillis);
throw new Error("timeout");
} |
Motivation
As part of #869, I wanted to convert an example by @thr1ve. in Discord to Structured Concurrency with Effection.
This is what it looks like in Effect.ts
It was pretty straightforward and fun to implement in Effection.
The logic is this:
This is what it looks like in Effection
It's longer, but it doesn't require tears.
Approach
retry-backoff.ts
example.TODO: