withStreamRetry<T> function

Future<T> withStreamRetry<T>(
  1. Future<T> fn(), {
  2. int maxRetries = 3,
  3. Duration initialDelay = const Duration(seconds: 1),
  4. Duration maxDelay = const Duration(seconds: 30),
  5. bool shouldRetry(
    1. Object error
    )?,
})

Execute an async operation with exponential backoff and jitter.

This is a streaming-aware retry wrapper that complements the existing withRetry in retry.dart. It operates on raw futures rather than ApiError types, making it suitable for use outside the ApiProvider abstraction.

Implementation

Future<T> withStreamRetry<T>(
  Future<T> Function() fn, {
  int maxRetries = 3,
  Duration initialDelay = const Duration(seconds: 1),
  Duration maxDelay = const Duration(seconds: 30),
  bool Function(Object error)? shouldRetry,
}) async {
  final random = Random();
  var attempt = 0;

  while (true) {
    try {
      return await fn();
    } catch (e) {
      attempt++;
      if (attempt >= maxRetries) rethrow;

      // Check custom retry predicate.
      if (shouldRetry != null && !shouldRetry(e)) rethrow;

      // Default: retry on ApiError if retryable, otherwise don't.
      if (shouldRetry == null) {
        if (e is ApiError && !e.isRetryable) rethrow;
      }

      // Calculate backoff with jitter.
      final baseMs = initialDelay.inMilliseconds * pow(2, attempt - 1);
      final jitter = random.nextDouble() * 0.25 * baseMs;
      final delayMs = (baseMs + jitter).toInt().clamp(
        initialDelay.inMilliseconds,
        maxDelay.inMilliseconds,
      );

      await Future<void>.delayed(Duration(milliseconds: delayMs));
    }
  }
}