Runs the primary function and several competitors in parallel and returns the result of the first one to complete successfully.
Orchestration link
Orchestration decorators compose multiple function executions: racing them, running them in parallel, or chaining them with compensation.
What it is link
Runs the primary function and several competitors in parallel and returns the result of the first one to complete successfully.
When to use it link
- Primary/backup API selection
- Latency-sensitive reads
- Hedged requests
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ❌ | ✅ | ✅ | ❌ |
API reference link
1
2
3
4
5
6
7
| // api-reference
RaceExtension1<T, R>(
Func1<T, R> primary, {
required List<Func1<T, R>> competitors,
void Function(int index, R result)? onWin,
void Function(int index, R result)? onLose,
})
|
For Func2:
1
2
3
4
5
6
7
| // api-reference
RaceExtension2<T1, T2, R>(
Func2<T1, T2, R> primary, {
required List<Func2<T1, T2, R>> competitors,
void Function(int index, R result)? onWin,
void Function(int index, R result)? onLose,
})
|
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
11
12
13
| final fastest = RaceExtension1<String, String>(
Func1((_) async {
await Future<void>.delayed(Duration(milliseconds: 100));
return 'primary';
}),
competitors: [
Func1((_) async => 'backup'),
],
);
void main() async {
print(await fastest('x')); // backup
}
|
Real world
1
2
3
4
5
6
7
8
9
| final fetchQuote = RaceExtension1<String, Quote>(
Func1((symbol) async => await primaryExchange.quote(symbol) as Quote),
competitors: [
Func1((symbol) async => await backupExchange.quote(symbol) as Quote),
],
onWin: (index, quote) => metrics.increment('backup_quote_wins'),
);
await fetchQuote('AAPL');
|
Best practices link
- Use competitors with similar semantics so any result is valid.
- Combine with
timeout on each competitor to avoid slow sources.
Common pitfalls link
- Losers continue running; their results are ignored.
- If all sources fail, the first error is thrown.
What it is link
Runs the primary function and additional functions in parallel, collecting all results in order.
When to use it link
- Parallel independent reads
- Bulk operations
- Scatter-gather
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ❌ | ✅ | ✅ | ❌ |
API reference link
1
2
3
4
5
6
7
| // api-reference
AllExtension1<T, R>(
Func1<T, R> primary, {
required List<Func1<T, R>> functions,
bool failFast = true,
void Function(int index, R result)? onComplete,
})
|
For Func2:
1
2
3
4
5
6
7
| // api-reference
AllExtension2<T1, T2, R>(
Func2<T1, T2, R> primary, {
required List<Func2<T1, T2, R>> functions,
bool failFast = true,
void Function(int index, R result)? onComplete,
})
|
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
11
| final combined = AllExtension1<int, int>(
Func1((n) async => n),
functions: [
Func1((n) async => n * 2),
Func1((n) async => n + 1),
],
);
void main() async {
print(await combined(3)); // [3, 6, 4]
}
|
Real world
1
2
3
4
5
6
7
8
9
10
11
| final enrichUser = AllExtension1<String, dynamic>(
Func1((id) async => await profileApi.get(id)),
functions: [
Func1((id) async => await ordersApi.recent(id)),
Func1((id) async => await preferencesApi.get(id)),
],
failFast: true,
onComplete: (index, result) => logger.info('Source $index complete'),
);
await enrichUser('user-123');
|
Best practices link
- Use
failFast: false only when you plan to inspect partial results yourself; all still throws the first error. - Keep functions independent to maximize parallelism.
Common pitfalls link
all returns List<R>; the primary result is at index 0.- When
failFast is true, the first error aborts the await.
What it is link
Executes a sequence of steps where each step can have a compensating action. If any step fails, completed steps are compensated in reverse order.
When to use it link
- Distributed transactions
- Multi-step workflows that must remain consistent
- Booking, payment, and inventory operations
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ❌ | ✅ | ✅ | ❌ |
API reference link
1
2
3
4
5
6
7
8
| // api-reference
SagaExtension1<T, R>(
Func1<T, R> initialStep, {
required List<SagaStep<dynamic, dynamic>> steps,
void Function(int index, dynamic result)? onCompensate,
void Function(int index, dynamic result)? onStepComplete,
void Function(int index, dynamic result, Object error)? onCompensationError,
})
|
For Func2:
1
2
3
4
5
6
| // api-reference
SagaExtension2<T1, T2, R>(
Func2<T1, T2, R> initialStep, {
required List<SagaStep<dynamic, dynamic>> steps,
...
})
|
SagaStep:
1
2
3
4
5
| // api-reference
SagaStep<T, R>({
required Func1<T, R> action,
required Func1<R, void> compensation,
})
|
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
11
12
13
| final saga = SagaExtension1<int, int>(
Func1((n) async => n),
steps: [
SagaStep<int, int>(
action: Func1<int, int>((int n) async => n + 1),
compensation: Func1<int, void>((int n) async => print('compensate $n')),
),
],
);
void main() async {
print(await saga(1)); // 2
}
|
Real world
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| final placeOrder = SagaExtension1<Order, Receipt>(
Func1((order) async => await orderService.create(order) as Receipt),
steps: [
SagaStep<Receipt, Charge>(
action: Func1<Receipt, Charge>(
(Receipt receipt) async => await paymentService.charge(receipt) as Charge,
),
compensation: Func1<Charge, void>(
(Charge charge) async => await paymentService.refund(charge),
),
),
SagaStep<Charge, Notification>(
action: Func1<Charge, Notification>(
(Charge charge) async =>
await notificationService.send(charge) as Notification,
),
compensation: Func1<Notification, void>(
(Notification msg) async => await notificationService.cancel(msg),
),
),
],
onCompensate: (index, result) => logger.info('Compensating step $index'),
onCompensationError: (index, result, error) =>
logger.error('Compensation failed', error),
);
await placeOrder(Order());
|
Best practices link
- Design compensations to be idempotent.
- Log all compensation errors; a failed compensation leaves the system partially inconsistent.
Common pitfalls link
- The initial step has no compensation; only
steps are compensated. - Compensation errors are caught and reported via
onCompensationError; the original exception is still thrown.