Skip to content
This repository has been archived by the owner on Oct 17, 2024. It is now read-only.

Async cache won't store exceptions #258

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## 2.12.0-wip

- Require Dart 3.4
* Can decide `fetch` method of `AsyncCache` will store exception or not.
* Require Dart 3.2

## 2.11.0

Expand Down
33 changes: 29 additions & 4 deletions lib/src/async_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class AsyncCache<T> {
/// Cached results of a previous [fetch] call.
Future<T>? _cachedValueFuture;

/// Whether the cache will keep a future completed with an error.
///
/// If `false`, a non-ephemeral cache will clear the cached future
/// immediately if the future completes with an error, as if the
/// caching was ephemeral.
/// _(Ephemeral caches always clear when the future completes,
/// so this flag has no effect on those.)_
final bool _cacheErrors;

/// Fires when the cache should be considered stale.
Timer? _stale;

Expand All @@ -44,14 +53,20 @@ class AsyncCache<T> {
/// The [duration] starts counting after the Future returned by [fetch]
/// completes, or after the Stream returned by `fetchStream` emits a done
/// event.
AsyncCache(Duration duration) : _duration = duration;
/// If [cacheErrors] is `false` the cache will be invalidated if the [Future]
/// returned by the callback completes as an error.
AsyncCache(Duration duration, {bool cacheErrors = true})
akmalviya03 marked this conversation as resolved.
Show resolved Hide resolved
: _duration = duration,
_cacheErrors = cacheErrors;

/// Creates a cache that invalidates after an in-flight request is complete.
///
/// An ephemeral cache guarantees that a callback function will only be
/// executed at most once concurrently. This is useful for requests for which
/// data is updated frequently but stale data is acceptable.
AsyncCache.ephemeral() : _duration = null;
AsyncCache.ephemeral()
: _duration = null,
_cacheErrors = true;

/// Returns a cached value from a previous call to [fetch], or runs [callback]
/// to compute a new one.
Expand All @@ -62,8 +77,18 @@ class AsyncCache<T> {
if (_cachedStreamSplitter != null) {
throw StateError('Previously used to cache via `fetchStream`');
}
return _cachedValueFuture ??= callback()
..whenComplete(_startStaleTimer).ignore();
if (_cacheErrors) {
return _cachedValueFuture ??= callback()
..whenComplete(_startStaleTimer).ignore();
} else {
return _cachedValueFuture ??= callback().then((value) {
_startStaleTimer();
return value;
}, onError: (Object error, StackTrace stack) {
invalidate();
throw error;
});
}
}

/// Returns a cached stream from a previous call to [fetchStream], or runs
Expand Down
14 changes: 14 additions & 0 deletions test/async_cache_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ void main() {
cache = AsyncCache(const Duration(hours: 1));
});

test('should not fetch when callback throws exception', () async {
cache = AsyncCache(const Duration(hours: 1), cacheErrors: false);

Future<String> asyncFunctionThatThrows() {
throw Exception();
}

var errorThrowingFuture = cache.fetch(asyncFunctionThatThrows);
await expectLater(errorThrowingFuture, throwsA(isException));

var valueFuture = cache.fetch(() async => 'Success');
expect(await valueFuture, 'Success');
});

test('should fetch via a callback when no cache exists', () async {
expect(await cache.fetch(() async => 'Expensive'), 'Expensive');
});
Expand Down