exponential_back_off 0.1.0 exponential_back_off: ^0.1.0 copied to clipboard
Implementation of exponential backoff algorithm using pure dart
Retry failing processes like HTTP requests using an exponential interval between each retry #
Exponential backoff algorithm:
- An exponential backoff algorithm retries requests exponentially, increasing the waiting time between retries up to a maximum backoff time.
Features #
- ✅ Start process
- ✅ Stop process
- ✅ Reset Process
- ✅ Set max attempts
- ✅ Set max elapsed time
- ✅ Set max delay between retries
- ✅ Conditional retry
- ✅ On retry callback
- ✅ Tweak the exponential delay parameters
- ✅ Specify the amount of randomness for the delays
- ✅ Custom delay algorithm (inherit from the base class
BackOff
)
Getting started #
- Add the package to your
pubspec.yaml
dependencies:
exponential_back_off: ^x.y.z
- Import
exponential_back_off
in a dart file.
import 'package:exponential_back_off/exponential_back_off.dart';
Usage #
- Create
ExponentialBackOff
object
final exponentialBackOff = ExponentialBackOff();
- Make a request
final result = await exponentialBackOff.start<Response>(
() => http.get(Uri.parse('https://www.gnu.org/')),
);
-
Handle the result
You can handel the result in two ways:- By checking if the result
isLeft
orisRight
. and get the value accordingly. - Using the fold function
result.fold((error){},(data){})
. The fold function will call the first(Left) function if the result is error otherwise will call second function(Right) if the result is data.
The error will always be in Left and the data will always be in Right
- Using if check:
result.fold( (error) { //Left(Exception): handel the error print(error); }, (response) { //Right(Response): handel the result print(response.body); }, );
- Using fold:
result.fold( (error) { //Left(Exception): handel the error print(error); }, (response) { //Right(Response): handel the result print(response.body); }, );
- By checking if the result
NOTE
With the default configuration it will be retried up-to 10 times, sleeping 1st, 2nd, 3rd, ..., 9th attempt: (will not sleep the 10th)
randomPercent: >=0.0% <=15%
1. 400 ms +/- (randomPercent of 400 ms)
2. 800 ms +/- (randomPercent of 800 ms)
3. 1600 ms +/- (randomPercent of 1600 ms)
4. 3200 ms +/- (randomPercent of 3200 ms)
5. 6400 ms +/- (randomPercent of 6400 ms)
6. 12800 ms +/- (randomPercent of 12800 ms)
7. 25600 ms +/- (randomPercent of 25600 ms)
8. 51200 ms +/- (randomPercent of 51200 ms)
9. 102400 ms +/- (randomPercent of 102400 ms)
10. 204800 ms +/- (randomPercent of 204800 ms) **will not sleep it**
Examples #
Because we love to see examples in the README (:
- Simple use case with the default configuration:
final exponentialBackOff = ExponentialBackOff();
/// The result will be of type [Either<Exception, Response>]
final result = await exponentialBackOff.start<Response>(
// Make a request
() {
return get(Uri.parse('https://www.gnu.org/'))
.timeout(Duration(seconds: 10));
},
// Retry on SocketException or TimeoutException and other then that the process
// will stop and return with the error
retryIf: (e) => e is SocketException || e is TimeoutException,
);
/// You can handel the result in two ways
/// * By checking if the result `isLeft` or `isRight`. and get the value accordingly.
/// * Using the fold function `result.fold((error){},(data){})`. will call the
/// first(Left) function if the result is error otherwise will call second
/// function(Right) if the result is data.
///
/// The error will always be in Left and the data will always be in Right
// using if check
if (result.isLeft()) {
//Left(Exception): handel the error
final error = result.getLeftValue();
print(error);
} else {
//Right(Response): handel the result
final response = result.getRightValue();
print(response.body);
}
// using fold:
result.fold(
(error) {
//Left(Exception): handel the error
print(error);
},
(response) {
//Right(Response): handel the result
print(response.body);
},
);
- Reusing the same object with the default configuration:
// reset will call stop() and reset everything to zero
await exponentialBackOff.reset();
print('interval: ' + exponentialBackOff.interval.toString());
print('max randomization factor: ' +
exponentialBackOff.maxRandomizationFactor.toString());
print('max attempts: ' + exponentialBackOff.maxAttempts.toString());
print('max delay: ' + exponentialBackOff.maxDelay.toString());
print('max elapsed time: ' + exponentialBackOff.maxElapsedTime.toString());
print('==================================================================');
await exponentialBackOff.start(
() => get(Uri.parse('https://www.gnu.org/')).timeout(
Duration.zero, // it will always throw TimeoutException
),
retryIf: (e) => e is SocketException || e is TimeoutException,
onRetry: (error) {
print('attempt: ' + exponentialBackOff.attemptCounter.toString());
print('error: ' + error.toString());
print('current delay: ' + exponentialBackOff.currentDelay.toString());
print('elapsed time: ' + exponentialBackOff.elapsedTime.toString());
print('--------------------------------------------------------');
},
);
- Tweaks the exponential delay parameters:
// Will be retried up-to 5 times,
// sleeping 1st, 2nd, 3rd, ..., 4th attempt: (will not sleep the 5th)
//
// randomPercent: 0.0%
//
// 1. 200 ms
// 2. 400 ms
// 3. 800 ms
// 4. 1600 ms
// 5. 3200 ms **will not sleep it**
final exponentialBackOff = ExponentialBackOff(
interval: Duration(milliseconds: 100),
maxAttempts: 5,
maxRandomizationFactor: 0.0,
maxDelay: Duration(seconds: 15),
);
print('interval: ' + exponentialBackOff.interval.toString());
print('max randomization factor: ' +
exponentialBackOff.maxRandomizationFactor.toString());
print('max attempts: ' + exponentialBackOff.maxAttempts.toString());
print('max delay: ' + exponentialBackOff.maxDelay.toString());
print('max elapsed time: ' + exponentialBackOff.maxElapsedTime.toString());
print('==================================================================');
await exponentialBackOff.start(
() => get(Uri.parse('https://www.gnu.org/')).timeout(
Duration.zero, // it will always throw TimeoutException
),
retryIf: (e) => e is SocketException || e is TimeoutException,
onRetry: (error) {
print('attempt: ' + exponentialBackOff.attemptCounter.toString());
print('error: ' + error.toString());
print('current delay: ' + exponentialBackOff.currentDelay.toString());
print('elapsed time: ' + exponentialBackOff.elapsedTime.toString());
print('--------------------------------------------------------');
},
);
- Custom Delay
-
create a subclass from Backoff base class
/// linier delays: /// /// 1. 1000 ms /// 2. 2000 ms /// 3. 3000 ms /// 4. 4000 ms /// 5. 5000 ms class CustomDelay extends BackOff { CustomDelay({ super.maxAttempts, super.maxDelay, super.maxElapsedTime, }) : assert(maxAttempts != null || maxElapsedTime != null, 'Can not have both maxAttempts and maxElapsedTime null'); @override Duration computeDelay(int attempt, Duration elapsedTime) { return Duration(seconds: attempt); } }
-
Use it as you normally do
// Will be retried up-to 5 times, // sleeping 1st, 2nd, 3rd, ..., 4th attempt: (will not sleep the 5th) // // 1. 1000 ms // 2. 2000 ms // 3. 3000 ms // 4. 4000 ms // 5. 5000 ms **will not sleep it** final customDelay = CustomDelay(maxAttempts: 5); print('max attempts: ' + customDelay.maxAttempts.toString()); print('max delay: ' + customDelay.maxDelay.toString()); print('max elapsed time: ' + customDelay.maxElapsedTime.toString()); print('=================================================================='); await customDelay.start( () => get(Uri.parse('https://www.gnu.org/')).timeout( Duration.zero, // it will always throw TimeoutException ), retryIf: (e) => e is SocketException || e is TimeoutException, onRetry: (error) { print('attempt: ' + customDelay.attemptCounter.toString()); print('error: ' + error.toString()); print('current delay: ' + customDelay.currentDelay.toString()); print('elapsed time: ' + customDelay.elapsedTime.toString()); print('--------------------------------------------------------'); }, );
-