.claude/skills/asyncredux-retry-mixin/SKILL.md
Add the Retry mixin for automatic retry with exponential backoff on action failure. Covers using Retry alone for limited retries, combining with UnlimitedRetries for infinite retries, and configuring retry behavior.
npx skillsauth add marcglasberg/async_redux asyncredux-retry-mixinInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The Retry mixin automatically retries failed actions using exponential backoff. When an error occurs in the reduce() method, the action is re-executed with progressively increasing delays between attempts.
Add the Retry mixin to any action that should automatically retry on failure:
class LoadDataAction extends AppAction with Retry {
Future<AppState?> reduce() async {
var data = await fetchDataFromServer();
return state.copy(data: data);
}
}
With this mixin:
Override these getters to customize retry behavior:
class LoadDataAction extends AppAction with Retry {
// Delay before first retry (default: 350ms)
int get initialDelay => 500;
// Multiplier for delay growth (default: 2)
int get multiplier => 2;
// Maximum retry attempts (default: 3)
int get maxRetries => 5;
// Upper limit on delay to prevent excessive waits (default: 5000ms)
int get maxDelay => 10000;
Future<AppState?> reduce() async {
var data = await fetchDataFromServer();
return state.copy(data: data);
}
}
| Parameter | Default | Purpose |
|-----------|---------|---------|
| initialDelay | 350 ms | Waiting period before first retry |
| multiplier | 2 | Growth factor for delays between attempts |
| maxRetries | 3 | Maximum retry count (total executions = maxRetries + 1) |
| maxDelay | 5 sec | Upper limit on delay to prevent excessive waits |
With default settings (initialDelay=350ms, multiplier=2, maxRetries=3):
Retry delays start after the reducer finishes, not from when the action was dispatched. If reduce() takes 1 second to fail and initialDelay is 350ms, the first retry starts 1.35 seconds after the action began.
Access the attempts getter within your action to know which attempt is currently running:
class LoadDataAction extends AppAction with Retry {
Future<AppState?> reduce() async {
print('Attempt ${attempts + 1}'); // 0-indexed, so first attempt is 0
if (attempts > 0) {
// Maybe try a different server on retries
return state.copy(data: await fetchFromBackupServer());
}
return state.copy(data: await fetchFromPrimaryServer());
}
}
Combine UnlimitedRetries with Retry to retry indefinitely until the action succeeds:
class CriticalSyncAction extends AppAction with Retry, UnlimitedRetries {
Future<AppState?> reduce() async {
await syncCriticalData();
return state.copy(syncComplete: true);
}
}
This is equivalent to setting maxRetries to -1.
Warning: Using await dispatchAndWait(action) with UnlimitedRetries may hang indefinitely if the action continues failing. Use with caution and consider whether the action has a realistic chance of eventually succeeding.
The Retry mixin only retries when errors occur in the reduce() method. Failures in the before() method do not trigger retries - they fail immediately.
class LoadDataAction extends AppAction with Retry {
@override
Future<void> before() async {
// Errors here will NOT trigger retry - action fails immediately
await validatePermissions();
}
Future<AppState?> reduce() async {
// Only errors here trigger the retry mechanism
return state.copy(data: await fetchData());
}
}
All actions using the Retry mixin become asynchronous, regardless of their original synchronous nature. This is because the retry mechanism needs to wait between attempts.
Most actions using Retry should also include the NonReentrant mixin to prevent multiple instances from running simultaneously:
class SaveDataAction extends AppAction with NonReentrant, Retry {
Future<AppState?> reduce() async {
await saveToServer();
return state.copy(saved: true);
}
}
This prevents scenarios where:
For network operations, combine Retry with CheckInternet to ensure connectivity before attempting the action:
class FetchUserProfile extends AppAction with CheckInternet, Retry {
Future<AppState?> reduce() async {
var profile = await api.getUserProfile();
return state.copy(profile: profile);
}
}
The CheckInternet mixin runs first. If there's no connection, the action fails immediately without attempting retries.
class FetchProductsAction extends AppAction with Retry {
int get maxRetries => 3;
int get initialDelay => 500;
Future<AppState?> reduce() async {
var products = await api.getProducts();
return state.copy(products: products);
}
}
class SyncPendingChanges extends AppAction with Retry, UnlimitedRetries {
int get initialDelay => 1000;
int get maxDelay => 30000; // Cap at 30 seconds between retries
Future<AppState?> reduce() async {
await syncService.pushPendingChanges();
return state.copy(hasPendingChanges: false);
}
}
class ProcessPaymentAction extends AppAction with NonReentrant, Retry {
final double amount;
ProcessPaymentAction(this.amount);
int get maxRetries => 5;
int get initialDelay => 1000;
int get multiplier => 2;
int get maxDelay => 10000;
Future<AppState?> reduce() async {
var result = await paymentGateway.process(amount);
return state.copy(paymentStatus: result.status);
}
}
Compatible with:
CheckInternetNoDialogAbortWhenNoInternetNonReentrantThrottleDebounceCan be combined with:
UnlimitedRetries (enables infinite retries)class RobustApiAction extends AppAction
with CheckInternet, NonReentrant, Retry {
// Retry configuration
int get initialDelay => 500; // 500ms before first retry
int get multiplier => 2; // Double delay each time
int get maxRetries => 4; // Try up to 5 times total
int get maxDelay => 8000; // Never wait more than 8 seconds
Future<AppState?> reduce() async {
if (attempts > 0) {
print('Retry attempt $attempts');
}
var data = await api.fetchCriticalData();
return state.copy(data: data);
}
}
URLs from the documentation:
data-ai
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
data-ai
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
testing
Handle user-facing errors with UserException. Covers throwing UserException from actions, setting up UserExceptionDialog, customizing error dialogs with `onShowUserExceptionDialog`, and using UserExceptionAction for non-interrupting error display.
tools
Implement undo/redo functionality using state observers. Covers recording state history with stateObserver, creating a RecoverStateAction, implementing undo for the full state or partial state, and managing history limits.