Delays execution until a specified duration has passed without a new call. Each new call cancels the pending execution and restarts the timer.
Timing decorators control when a function executes. Use them to debounce search inputs, throttle button clicks, add deliberate delays, or enforce deadlines.
debounce link
What it is link
Delays execution until a specified duration has passed without a new call. Each new call cancels the pending execution and restarts the timer.
When to use it link
- Search-as-you-type
- Resize handlers
- Any event that fires rapidly and only the final state matters
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ✅ | ✅ | ✅ | ❌ |
API reference link
1
2
| // api-reference
Func<R> debounce(Duration duration, {DebounceMode mode = DebounceMode.trailing})
|
duration — quiet period required before execution.mode — trailing (default), leading, or both.cancel() — cancels the pending execution and resets state.
DebounceMode:
trailing — execute after the quiet period.leading — execute immediately on the first call, ignore subsequent calls until the window resets.both — execute on the first call and again after the quiet period.
Examples link
Minimal
1
2
3
4
5
6
7
8
| final search = Func<String>(() async => 'results')
.debounce(Duration(milliseconds: 300));
void main() async {
search();
search();
print(await search()); // Only the last call runs after 300ms
}
|
Real world
1
2
3
4
5
6
7
8
9
| final searchUsers = Func1<String, List<User>>((query) async {
return (await api.searchUsers(query)) as List<User>;
}).debounce(
Duration(milliseconds: 300),
mode: DebounceMode.trailing,
);
// Later:
await searchUsers('kim');
|
Best practices link
- Use
trailing for search inputs; use leading for actions where the first click matters. - Always handle the
StateError thrown by leading mode when called inside the debounce window.
Common pitfalls link
leading mode throws StateError('Function is debounced') if called again inside the window.- For
Func, DebounceMode.both schedules a trailing execution only after a leading execution has occurred; the implementation differs subtly from Func1/Func2.
throttle link
What it is link
Limits execution to at most once per duration. Unlike debounce, throttle enforces a minimum interval between executions.
When to use it link
- Button clicks
- Scroll events
- API calls that must stay under a rate limit
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ✅ | ✅ | ✅ | ❌ |
API reference link
1
2
| // api-reference
Func<R> throttle(Duration duration, {ThrottleMode mode = ThrottleMode.leading})
|
duration — minimum time between executions.mode — leading (default), trailing, or both.reset() — clears the last execution time and pending trailing execution.
ThrottleMode:
leading — execute immediately, block calls inside the window.trailing — schedule execution at the end of the window.both — execute immediately and again at window end.
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
11
| final submit = Func<String>(() async => 'ok')
.throttle(Duration(seconds: 1));
void main() async {
print(await submit()); // ok
try {
await submit(); // throws inside window
} on StateError catch (e) {
print(e);
}
}
|
Real world
1
2
3
4
5
6
7
8
9
| final updateLocation = Func1<Location, void>((location) async {
await api.updateLocation(location);
}).throttle(
Duration(seconds: 5),
mode: ThrottleMode.leading,
);
// Usage:
await updateLocation(Location());
|
Best practices link
- Use
leading for actions that should respond immediately. - Use
trailing when you want the latest call inside a window to win.
Common pitfalls link
leading mode throws StateError('Function is throttled') when called inside the throttle window.both mode returns the leading result and schedules a trailing execution as a side effect; callers awaiting the trailing call need a separate handle.
What it is link
Inserts a pause before and/or after function execution.
When to use it link
- Pacing API calls
- UI animations
- Adding breathing room between retries
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ✅ | ✅ | ✅ | ❌ |
API reference link
1
2
| // api-reference
Func<R> delay(Duration duration, {DelayMode mode = DelayMode.before})
|
DelayMode:
before — wait before calling the function.after — wait after the function completes.both — wait before and after.
Examples link
Minimal
1
2
3
4
5
6
| final slow = Func<String>(() async => 'done')
.delay(Duration(milliseconds: 500));
void main() async {
print(await slow()); // waits 500ms, prints done
}
|
Real world
1
2
3
4
5
| final poll = Func<Status>(() async => (await api.status()) as Status)
.delay(Duration(seconds: 2), mode: DelayMode.after)
.repeat(times: 10);
await poll();
|
Best practices link
- Combine
delay with repeat for simple polling loops. - Prefer
debounce or throttle over delay when you need to coalesce rapid calls.
Common pitfalls link
delay is per-call; it does not serialize calls or enforce a global rate.
What it is link
Enforces a maximum execution time. If the wrapped function does not complete within the duration, a TimeoutException is thrown (unless an onTimeout callback is provided).
When to use it link
- Network requests
- Long-running computations
- Any operation that could hang
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ✅ | ✅ | ✅ | ❌ |
API reference link
1
2
3
4
5
| // api-reference
Func<R> timeout(
Duration duration, {
FutureOr<R> Function()? onTimeout,
})
|
duration — maximum time allowed.onTimeout — optional callback returning a fallback value instead of throwing.
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
11
12
| final fetch = Func<String>(() async {
await Future<void>.delayed(Duration(seconds: 2));
return 'data';
}).timeout(Duration(milliseconds: 100));
void main() async {
try {
await fetch();
} on TimeoutException {
print('timed out');
}
}
|
Real world
1
2
3
4
5
6
7
8
| final fetchProfile = Func1<String, Profile>((id) async {
return (await api.getProfile(id)) as Profile;
}).timeout(
Duration(seconds: 5),
onTimeout: () async => Profile(),
);
await fetchProfile('user-123');
|
Best practices link
- Place
timeout carefully in decorator chains; wrapping retry with timeout gives a global deadline. - Provide
onTimeout for non-critical paths to avoid exceptions.
Common pitfalls link
timeout does not cancel the underlying future; the wrapped work continues in the background.- Without
onTimeout, TimeoutException is thrown.
What it is link
Lazily evaluates the function the first time the returned Future is awaited. Multiple awaits on the same future execute only once.
When to use it link
- Expensive initialization that may never be needed
- Lazy singleton construction
- Deferring work until a value is actually consumed
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ✅ | ✅ | ✅ | ❌ |
API reference link
1
2
| // api-reference
Func<R> asDeferred()
|
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
11
| final lazy = Func<String>(() async {
print('computing');
return 'value';
}).asDeferred();
void main() async {
final f1 = lazy();
final f2 = lazy();
print(await f1); // computing, value
print(await f2); // value (no recomputation)
}
|
Real world
1
2
3
4
5
| final heavyConfig = Func<Config>(() async => configRepository.load() as Config)
.asDeferred();
final config = await heavyConfig();
print(config);
|
Best practices link
- Use
defer when the caller may or may not need the result. - Combine with
memoize if the result should be cached beyond the first future.
Common pitfalls link
- Errors are cached too: if the deferred computation fails, the same error is returned on subsequent awaits.
idleCallback link
What it is link
Executes the function only when an IdleDetector returns true. By default the detector always returns true, so the decorator is mainly useful with a custom detector.
When to use it link
- Background tasks that should run only when the app/device is idle
- Non-urgent cleanup or prefetching
Async / sync support link
Func<R> | Func1<T, R> | Func2<T1, T2, R> | FuncSync<R> |
|---|
| ✅ | ✅ | ✅ | ❌ |
API reference link
1
2
3
4
5
| // api-reference
Func<R> idleCallback({
Duration checkInterval = Duration(milliseconds: 100),
IdleDetector idleDetector = defaultIdleDetector,
})
|
checkInterval — how often to poll the detector.idleDetector — bool Function() returning true when idle.
Examples link
Minimal
1
2
3
4
5
6
7
8
9
10
| bool idle = false;
final task = Func<String>(() async => 'done').idleCallback(
idleDetector: () => idle,
);
void main() async {
idle = true;
print(await task());
}
|
Real world
1
2
3
4
5
6
7
8
9
10
11
| var cpuLoad = 0.1;
var networkMetered = false;
final prefetch = Func<Recommendations>(() async {
return (await api.fetchRecommendations()) as Recommendations;
}).idleCallback(
checkInterval: Duration(seconds: 1),
idleDetector: () => cpuLoad < 0.2 && !networkMetered,
);
await prefetch();
|
Best practices link
- Keep the detector cheap; it is polled repeatedly.
- Use a reasonable
checkInterval to avoid busy-waiting.
Common pitfalls link
- If the detector never returns
true, the call hangs indefinitely.