Skip to content

Instantly share code, notes, and snippets.

@eric-taix
Last active September 9, 2025 13:41
Show Gist options
  • Select an option

  • Save eric-taix/071668a826358e60cfe3b5981dc4627e to your computer and use it in GitHub Desktop.

Select an option

Save eric-taix/071668a826358e60cfe3b5981dc4627e to your computer and use it in GitHub Desktop.
Generic retry strategy on TaskEither<L,R>
import 'dart:math';
import 'package:fpdart/fpdart.dart';
/// [RetryStrategy] is a strategy to retry a [TaskEither] when it fails.
/// See each implementation documentation for more details.
abstract interface class RetryStrategy {
TaskEither<L, R> retry<L, R>(TaskEither<L, R> taskEither);
}
/// [LinearRetryStrategy] retries a [TaskEither] linearly with a fixed delay between retries
/// and a maximum number of retries.
class LinearRetryStrategy implements RetryStrategy {
LinearRetryStrategy({required this.maxAttempts, required this.interval});
/// The total number of attempts to do the job.
final int maxAttempts;
/// The delay between retries.
final Duration interval;
int _attempts = 0;
@override
TaskEither<L, R> retry<L, R>(TaskEither<L, R> taskEither) {
TaskEither<L, R> doRetry(L error) {
_attempts++;
return _attempts > maxAttempts ? TaskEither.left(error) : taskEither.delay(interval).orElse(doRetry);
}
return taskEither.orElse(doRetry);
}
}
/// [ExponentialBackoffRetryStrategy] retries a [TaskEither] exponentially with a exponential delay between retries
/// and a maximum number of retries. The interval between retries is capped by [maxInterval] to avoid very long wait times.
/// See [FibonacciBackoffRetryStrategy] for a smoother retry strategy
class ExponentialBackoffRetryStrategy implements RetryStrategy {
ExponentialBackoffRetryStrategy({
required this.maxAttempts,
required this.initialInterval,
required this.multiplier,
required this.maxInterval,
});
/// The total maximum number of attemps to do the job
final int maxAttempts;
/// The initial delay between retries.
final Duration initialInterval;
/// The multiplier used to calculate the delay for each retry.
final double multiplier;
/// The maximum delay between retries.
final Duration maxInterval;
int _attempts = 1;
@override
TaskEither<L, R> retry<L, R>(TaskEither<L, R> taskEither) {
TaskEither<L, R> doRetry(L error) {
_attempts++;
final currentInterval = initialInterval * (pow(multiplier, _attempts - 1));
final delay = currentInterval.compareTo(maxInterval) < 0 ? currentInterval : maxInterval;
return _attempts > maxAttempts ? TaskEither.left(error) : taskEither.delay(delay).orElse(doRetry);
}
return taskEither.orElse(doRetry);
}
}
/// [FibonacciBackoffRetryStrategy] retries a [TaskEither] with a delay between retries that follows the Fibonacci sequence
/// and a maximum number of retries. The interval between retries is capped by [maxInterval] to avoid very long wait times.
///
/// [FibonacciBackoffRetryStrategy] has smoother intervals than [ExponentialBackoffRetryStrategy]
class FibonacciBackoffRetryStrategy implements RetryStrategy {
FibonacciBackoffRetryStrategy({required this.maxAttempts, required this.initialInterval, required this.maxInterval});
final int maxAttempts;
final Duration initialInterval;
final Duration maxInterval;
int _attempts = 1;
int _fibonacci(int n) {
if (n <= 1) return 1;
return _fibonacci(n - 1) + _fibonacci(n - 2);
}
@override
TaskEither<L, R> retry<L, R>(TaskEither<L, R> taskEither) {
TaskEither<L, R> doRetry(L error) {
_attempts++;
final currentInterval = initialInterval * _fibonacci(_attempts - 1);
final delay = currentInterval.compareTo(maxInterval) < 0 ? currentInterval : maxInterval;
return _attempts > maxAttempts ? TaskEither.left(error) : taskEither.delay(delay).orElse(doRetry);
}
return taskEither.orElse(doRetry);
}
}
extension RetryTaskEither<L, R> on TaskEither<L, R> {
TaskEither<L, R> withRetry(RetryStrategy strategy) => strategy.retry(this);
}
void main() {
final taskEither = TaskEither<String, Unit>(() async {
print('Doing Job ${DateTime.now()}');
return const Left<String, Unit>('Error');
});
taskEither
.withRetry(
FibonacciBackoffRetryStrategy(
maxAttempts: 5,
initialInterval: const Duration(milliseconds: 300),
maxInterval: const Duration(seconds: 3),
),
)
.run();
taskEither
.withRetry(
ExponentialBackoffRetryStrategy(
maxAttempts: 5,
initialInterval: const Duration(milliseconds: 300),
multiplier: 2.2,
maxInterval: const Duration(seconds: 3),
),
)
.run();
taskEither
.withRetry(
LinearRetryStrategy(
maxAttempts: 5,
interval: const Duration(milliseconds: 300),
),
)
.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment