Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions retry/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v3.2.0
* `orElse` callback to calculate a value when the attempts where exhausted.

## v3.1.0
* Stable null-safety release.

Expand Down
21 changes: 20 additions & 1 deletion retry/lib/retry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,15 @@ class RetryOptions {
/// If no [retryIf] function is given this will retry any for any [Exception]
/// thrown. To retry on an [Error], the error must be caught and _rethrown_
/// as an [Exception].
///
/// If [orElse] is given and the [attemps] are exhausted by retryable
/// exceptions (either by default or via [retryIf]), the function will
/// return the result of [orElse].
Future<T> retry<T>(
FutureOr<T> Function() fn, {
FutureOr<bool> Function(Exception)? retryIf,
FutureOr<void> Function(Exception)? onRetry,
FutureOr<T> Function(Exception)? orElse,
}) async {
var attempt = 0;
// ignore: literal_only_boolean_expressions
Expand All @@ -132,6 +137,10 @@ class RetryOptions {
} on Exception catch (e) {
if (attempt >= maxAttempts ||
(retryIf != null && !(await retryIf(e)))) {
if (orElse != null) {
return await orElse(e);
}
Comment on lines +140 to +142
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two kinds of exceptions here:

  • (A) Exceptions that trigger a retry -- those where retryIf(e) returns true.
  • (B) Exceptions that don't trigger a retry.

There are two reasons a call to retry(fn, ...) may throw:

  • (i) fn throws an exception of type (B),
  • (ii) fn throws an exception of type (A) enough times that the number of retries have been exhausted.

Do we think it's right to call orElse in both cases (i) and (ii)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, this is an old one that is still open :)

I think we should only have an alternative callback for (ii), and with that in mind, orElse is not the best name for it. How about afterExhausted or onAttemptsExhausted?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think orElse is an okay name, but we might have to document the behavior -- if we want this at all.


rethrow;
}
if (onRetry != null) {
Expand Down Expand Up @@ -171,6 +180,10 @@ class RetryOptions {
/// If no [retryIf] function is given this will retry any for any [Exception]
/// thrown. To retry on an [Error], the error must be caught and _rethrown_
/// as an [Exception].
///
/// If [orElse] is given and the [attemps] are exhausted by retryable
/// exceptions (either by default or via [retryIf]), the function will
/// return the result of [orElse].
Future<T> retry<T>(
FutureOr<T> Function() fn, {
Duration delayFactor = const Duration(milliseconds: 200),
Expand All @@ -179,10 +192,16 @@ Future<T> retry<T>(
int maxAttempts = 8,
FutureOr<bool> Function(Exception)? retryIf,
FutureOr<void> Function(Exception)? onRetry,
FutureOr<T> Function(Exception)? orElse,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation needed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

}) =>
RetryOptions(
delayFactor: delayFactor,
randomizationFactor: randomizationFactor,
maxDelay: maxDelay,
maxAttempts: maxAttempts,
).retry(fn, retryIf: retryIf, onRetry: onRetry);
).retry(
fn,
retryIf: retryIf,
onRetry: onRetry,
orElse: orElse,
);
2 changes: 1 addition & 1 deletion retry/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: retry
version: 3.1.0
version: 3.2.0
description: |
Utility for wrapping an asynchronous function in automatic retry logic with
exponential back-off, useful when making requests over network.
Expand Down
30 changes: 30 additions & 0 deletions retry/test/retry_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,35 @@ void main() {
await expectLater(f, throwsA(isException));
expect(count, equals(2));
});

test('retry + orElse', () async {
var count = 0;
final v = await retry(
() async {
count++;
throw Exception();
},
orElse: (_) => 1,
maxAttempts: 3,
);
expect(count, 3);
expect(v, 1);
});

test('retry + orElse throws', () async {
var count = 0;
await expectLater(
() => retry(
() async {
count++;
throw Exception('fn');
},
orElse: (_) => throw FormatException('orElse'),
maxAttempts: 3,
),
throwsA(isA<FormatException>()),
);
expect(count, 3);
});
});
}