deadline_future 0.1.0
deadline_future: ^0.1.0 copied to clipboard
A Dart package that provides graceful timeout handling by returning cached or fallback values instead of throwing exceptions, ensuring resilient real-time applications and uninterrupted user experiences.
deadline_future π #
A time-bounded Future that never throws
TimeoutException.
Returns the freshest data available β live, cached, or a static fallback β instead of crashing.
The Problem #
// β Dart's built-in Future.timeout β "all or nothing"
try {
final price = await fetchPrice().timeout(const Duration(seconds: 2));
} on TimeoutException {
// The result was discarded even if it arrived 1ms later.
// You must handle the exception every single time.
}
In real-time apps this is painful: every slow network spike crashes the UI, every late response is wasted, and you have no visibility into why the fallback was used.
The Solution #
// β
deadline_future β three-tier graceful fallback
final result = await fetchPrice().withDeadline(
const Duration(seconds: 2),
fallback: lastKnownPrice, // π‘οΈ tier 3: static safety net
cacheKey: 'btc_price', // πΎ tier 2: automatic smart cache
cacheTtl: const Duration(minutes: 5),
onTimeout: () => showSpinner(), // called the moment deadline hits
context: 'BTC price widget', // appears in logs & exceptions
);
// result is ALWAYS available β never null, never an exception
switch (result.source) {
case DeadlineResultSource.completed:
print('β
Live β ${result.actualDuration!.inMilliseconds}ms');
case DeadlineResultSource.cached:
print('πΎ Cached β showing last known value');
case DeadlineResultSource.fallback:
print('π‘οΈ Fallback β network is struggling');
}
if (result.isDegraded) showStaleBadge(); // one-liner UI indicator
updatePrice(result.value); // always works
Resolution Strategy #
withDeadline(deadline, fallback: F, cacheKey: K)
β
ββ Future completes in time? β β
live result
β (stored in cache for next time)
β
ββ Timeout + cache[K] valid? β πΎ cached result
β
ββ Timeout + F != null? β π‘οΈ fallback result
β
ββ Timeout + nothing available? β π΄ DeadlineExceededException
Self-healing cache: even after a timeout, the original Future keeps running. When it finally completes, its value is stored in the cache β automatically improving the next call.
Installation #
dependencies:
deadline_future: ^0.1.0
dart pub get
Quick-start Recipes #
Minimal β static fallback only #
final result = await fetchUserProfile().withDeadline(
const Duration(seconds: 2),
fallback: UserProfile.guest(),
);
print(result.value.displayName); // always available
Smart cache β best for repeated calls #
// First call: Future wins β cached.
await fetchBtcPrice().withDeadline(
const Duration(seconds: 2),
cacheKey: 'btc',
cacheTtl: const Duration(minutes: 5),
);
// Second call: network degraded β served from cache.
final r = await fetchBtcPrice().withDeadline(
const Duration(milliseconds: 300),
cacheKey: 'btc',
fallback: 0.0,
);
Duration shorthand #
// Clean, readable deadlines:
await fetch().withDeadline(3.seconds);
await fetch().withDeadline(500.milliseconds);
await fetch().withDeadline(2.minutes);
Batch concurrent calls #
final results = await [fetchBtc(), fetchEth(), fetchSol()]
.withDeadlineAll(
const Duration(milliseconds: 500),
cacheKeys: ['btc', 'eth', 'sol'],
fallback: 0.0,
onTimeout: (i) => print('Feed $i timed out'),
);
Exception handling #
try {
await myFuture.withDeadline(const Duration(seconds: 1));
} on DeadlineExceededException catch (e) {
// Only thrown when NO cache entry AND NO fallback exist.
print('Exceeded ${e.deadline.inMilliseconds}ms β ${e.context}');
} on InvalidDeadlineDurationException {
// Synchronous guard against Duration.zero / negative values.
}
Global configuration (app startup) #
void main() {
DeadlineConfig.enableGlobalCache = true;
DeadlineConfig.defaultCacheTtl = const Duration(minutes: 10);
DeadlineConfig.maxCacheEntries = 500;
DeadlineConfig.logLevel = kDebugMode
? DeadlineLogLevel.info
: DeadlineLogLevel.silent;
runApp(const MyApp());
}
API Reference #
Future<T>.withDeadline() #
| Parameter | Type | Required | Description |
|---|---|---|---|
deadline |
Duration |
β | Max wait time. Must be positive. |
fallback |
T? |
Static value returned on timeout (if cache miss). | |
cacheKey |
String? |
Enables smart cache. Unique per call site. | |
cacheTtl |
Duration? |
Per-call TTL. Overrides defaultCacheTtl. |
|
onTimeout |
void Function()? |
Called the instant the deadline elapses. | |
context |
String? |
Label for logs and exception messages. |
Returns: Future<DeadlineResult<T>>
DeadlineResult<T> #
| Member | Type | Description |
|---|---|---|
value |
T |
The resolved value. |
isTimedOut |
bool |
Did the deadline elapse? |
source |
DeadlineResultSource |
completed, cached, or fallback. |
isLive |
bool |
Shorthand: source == completed. |
isDegraded |
bool |
Shorthand: !isLive. |
isFromCache |
bool |
Shorthand: source == cached. |
isFromFallback |
bool |
Shorthand: source == fallback. |
actualDuration |
Duration? |
How long the original Future took. |
resolvedAt |
DateTime |
UTC timestamp of resolution. |
copyWith(...) |
DeadlineResult<T> |
Non-destructive field override. |
DeadlineConfig (static) #
| Property / Method | Default | Description |
|---|---|---|
enableGlobalCache |
true |
Master cache toggle. |
defaultCacheTtl |
null |
Default TTL for all cache entries. |
maxCacheEntries |
200 |
Cache capacity before FIFO eviction. |
ignoreErrorsAfterDeadline |
true |
Swallow late Future errors. |
logLevel |
silent |
Controls stdout diagnostic output. |
reset() |
β | Restores defaults + clears cache. |
clearCache() |
β | Empties the cache only. |
evictCacheEntry(key) |
β | Removes one entry by key. |
cacheSize |
β | Current number of live cache entries. |
Comparison Table #
| Feature | Future.timeout() |
withDeadline() |
|---|---|---|
| Future completes in time | β Value | β Value + metadata |
| Timeout with handler | β
onTimeout value |
β Fallback / cache |
| Timeout without handler | β TimeoutException |
πΆ DeadlineExceededException* |
| Late result | ποΈ Discarded | πΎ Cached for next call |
| Next call after timeout | β Crashes again | β Served from cache |
| Result metadata | β None | β
DeadlineResultSource |
onTimeout callback |
β | β |
| Global config | β | β
DeadlineConfig |
| Batch API | β | β
withDeadlineAll |
| Duration shorthand | β | β
3.seconds |
* Only thrown as a last resort β cache and fallback are checked first.
Ideal Use Cases #
- π Crypto / stock price feeds β show last known price while refreshing
- π¬ Chat heads β display cached messages while server is slow
- π Live dashboards β partial data is better than blank panels
- ποΈ Sports scores β stale score with "updating..." badge
- π Retry wrappers β compose with
withDeadlinefor per-attempt limits - π Any API call where "stale but available" beats "fresh but crashed"
Testing #
dart test
Run the examples:
dart run example/main.dart
Run the benchmarks:
dart run benchmark/throughput_bench.dart
License #
BSD-3-Clause Β© 2026 deadline_future contributors