From 8eff89f800705eb02b1bd0705cba0f9d9dc94dc4 Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Tue, 5 Dec 2023 15:01:23 +0530 Subject: [PATCH 01/14] fix: async cache storing exception --- lib/src/async_cache.dart | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index 6fc7cb0..b5716b8 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -62,8 +62,22 @@ class AsyncCache { if (_cachedStreamSplitter != null) { throw StateError('Previously used to cache via `fetchStream`'); } - return _cachedValueFuture ??= callback() - ..whenComplete(_startStaleTimer).ignore(); + if(_cachedValueFuture == null){ + try{ + ///First we run the callback then we assign the value received + ///from [callback] to the [_cachedValueFuture] + T value = await callback(); + _cachedValueFuture ??= Future.value(value); + _startStaleTimer(); + return _cachedValueFuture!; + ///If [callback] generated an exception then we should not cache data + ///And propagate exception to the place from where [fetch] is triggered + } catch (error){ + rethrow; + } + } else{ + return _cachedValueFuture!; + } } /// Returns a cached stream from a previous call to [fetchStream], or runs From 8c569f1ce5969963a5635b04657e4ed962d535bf Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Tue, 5 Dec 2023 15:39:35 +0530 Subject: [PATCH 02/14] test: added test case for exception --- test/async_cache_test.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/async_cache_test.dart b/test/async_cache_test.dart index 77bda7c..f83388f 100644 --- a/test/async_cache_test.dart +++ b/test/async_cache_test.dart @@ -18,6 +18,14 @@ void main() { cache = AsyncCache(const Duration(hours: 1)); }); + test('should not fetch when callback throws exception', () async { + Future asyncFunctionThatThrows() { + throw Exception(); + } + var cacheFuture = cache.fetch(asyncFunctionThatThrows); + await expectLater(cacheFuture,throwsA(isException)); + }); + test('should fetch via a callback when no cache exists', () async { expect(await cache.fetch(() async => 'Expensive'), 'Expensive'); }); From 8a7844fad713646ca4e2c32e96f66d1a6e0f21b8 Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Tue, 5 Dec 2023 15:43:53 +0530 Subject: [PATCH 03/14] doc: added changes to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccda9d8..60fdcb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.12.0-wip - Require Dart 2.19 +- `fetch` method of `AsyncCache` will no longer store exception. ## 2.11.0 From 4b9f39867602039b6e0cee230596b414fa941a9a Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Fri, 8 Dec 2023 22:35:27 +0530 Subject: [PATCH 04/14] feat: added new _canCacheException flag --- lib/src/async_cache.dart | 50 +++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index b5716b8..d5c0362 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -7,7 +7,9 @@ import 'dart:async'; import '../async.dart'; /// Runs asynchronous functions and caches the result for a period of time. -/// +/// If you doesn't want to cache Exception then you can set +// [_canCacheException] to false. +// /// This class exists to cover the pattern of having potentially expensive code /// such as file I/O, network access, or isolate computation that's unlikely to /// change quickly run fewer times. For example: @@ -36,6 +38,12 @@ class AsyncCache { /// Cached results of a previous [fetch] call. Future? _cachedValueFuture; + ///Default is set to true + ///If we set this variable to false + ///On the initial run, if callback returned the [Exception] + ///Next time, we can reRun the callback for the successful attempt. + final bool _canCacheException; + /// Fires when the cache should be considered stale. Timer? _stale; @@ -44,14 +52,18 @@ class AsyncCache { /// 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; + AsyncCache(Duration duration, {bool canCacheException = true}) + : _duration = duration, + _canCacheException = canCacheException; /// 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, + _canCacheException = true; /// Returns a cached value from a previous call to [fetch], or runs [callback] /// to compute a new one. @@ -62,21 +74,27 @@ class AsyncCache { if (_cachedStreamSplitter != null) { throw StateError('Previously used to cache via `fetchStream`'); } - if(_cachedValueFuture == null){ - try{ - ///First we run the callback then we assign the value received - ///from [callback] to the [_cachedValueFuture] - T value = await callback(); - _cachedValueFuture ??= Future.value(value); - _startStaleTimer(); + if (_canCacheException) { + return _cachedValueFuture ??= callback() + ..whenComplete(_startStaleTimer).ignore(); + } else { + if (_cachedValueFuture == null) { + try { + ///First we run the callback then we assign the value received + ///from [callback] to the [_cachedValueFuture] + T value = await callback(); + _cachedValueFuture ??= Future.value(value); + _startStaleTimer(); + return _cachedValueFuture!; + + ///If [callback] generated an exception then we should not cache data + ///And propagate exception to the place from where [fetch] is triggered + } catch (error) { + rethrow; + } + } else { return _cachedValueFuture!; - ///If [callback] generated an exception then we should not cache data - ///And propagate exception to the place from where [fetch] is triggered - } catch (error){ - rethrow; } - } else{ - return _cachedValueFuture!; } } From 065640ce14254852d8656c049b25a23d76da7886 Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Fri, 8 Dec 2023 22:35:42 +0530 Subject: [PATCH 05/14] doc: added changes to the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60fdcb0..25d526e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 2.12.0-wip - Require Dart 2.19 -- `fetch` method of `AsyncCache` will no longer store exception. +- Can decide `fetch` method of `AsyncCache` will store exception or not. ## 2.11.0 From b8f4f27d9305c38c61a8cc2bb8df3ae6aa4b2065 Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Sat, 9 Dec 2023 10:53:36 +0530 Subject: [PATCH 06/14] fix: removed local variable type --- lib/src/async_cache.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index d5c0362..0458ffb 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -82,13 +82,12 @@ class AsyncCache { try { ///First we run the callback then we assign the value received ///from [callback] to the [_cachedValueFuture] - T value = await callback(); + var value = await callback(); _cachedValueFuture ??= Future.value(value); _startStaleTimer(); return _cachedValueFuture!; - - ///If [callback] generated an exception then we should not cache data - ///And propagate exception to the place from where [fetch] is triggered + ///If [callback] generated an exception then we should not cache data + ///And propagate exception to the place from where [fetch] is triggered } catch (error) { rethrow; } From cf3c3b5d29a6fa325294fe8142454d2b4218b07c Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Wed, 20 Dec 2023 23:13:51 +0530 Subject: [PATCH 07/14] chore: renamed variable _canCacheException to the _cacheErrors --- lib/src/async_cache.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index 0458ffb..b8d67a1 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -42,7 +42,7 @@ class AsyncCache { ///If we set this variable to false ///On the initial run, if callback returned the [Exception] ///Next time, we can reRun the callback for the successful attempt. - final bool _canCacheException; + final bool _cacheErrors; /// Fires when the cache should be considered stale. Timer? _stale; @@ -52,9 +52,9 @@ class AsyncCache { /// 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, {bool canCacheException = true}) + AsyncCache(Duration duration, {bool cacheErrors = true}) : _duration = duration, - _canCacheException = canCacheException; + _cacheErrors = cacheErrors; /// Creates a cache that invalidates after an in-flight request is complete. /// @@ -63,7 +63,7 @@ class AsyncCache { /// data is updated frequently but stale data is acceptable. AsyncCache.ephemeral() : _duration = null, - _canCacheException = true; + _cacheErrors = true; /// Returns a cached value from a previous call to [fetch], or runs [callback] /// to compute a new one. @@ -74,7 +74,7 @@ class AsyncCache { if (_cachedStreamSplitter != null) { throw StateError('Previously used to cache via `fetchStream`'); } - if (_canCacheException) { + if (_cacheErrors) { return _cachedValueFuture ??= callback() ..whenComplete(_startStaleTimer).ignore(); } else { From 5ebda076d6136351a585280425deb2d09ef415d9 Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Wed, 20 Dec 2023 23:21:31 +0530 Subject: [PATCH 08/14] chore: fixed comments formatting and description --- lib/src/async_cache.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index b8d67a1..7c00148 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -38,10 +38,10 @@ class AsyncCache { /// Cached results of a previous [fetch] call. Future? _cachedValueFuture; - ///Default is set to true - ///If we set this variable to false - ///On the initial run, if callback returned the [Exception] - ///Next time, we can reRun the callback for the successful attempt. + /// Default is set to true + /// If this variable is set to false, + /// and callback gets completed with an error, + /// then the response will not get cached. final bool _cacheErrors; /// Fires when the cache should be considered stale. From 9ac4312636e1e45d9831e6b0b412705f632a1b80 Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Wed, 20 Dec 2023 23:28:18 +0530 Subject: [PATCH 09/14] chore: updated comment with the suggested comment --- lib/src/async_cache.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index 7c00148..a8bc879 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -38,10 +38,13 @@ class AsyncCache { /// Cached results of a previous [fetch] call. Future? _cachedValueFuture; - /// Default is set to true - /// If this variable is set to false, - /// and callback gets completed with an error, - /// then the response will not get cached. + /// 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. From edbb8163e2e0850f1ad03b074324578631027e0f Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Thu, 21 Dec 2023 00:00:40 +0530 Subject: [PATCH 10/14] fix: updated code with the suggested code. --- lib/src/async_cache.dart | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index a8bc879..c92a733 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -81,22 +81,13 @@ class AsyncCache { return _cachedValueFuture ??= callback() ..whenComplete(_startStaleTimer).ignore(); } else { - if (_cachedValueFuture == null) { - try { - ///First we run the callback then we assign the value received - ///from [callback] to the [_cachedValueFuture] - var value = await callback(); - _cachedValueFuture ??= Future.value(value); - _startStaleTimer(); - return _cachedValueFuture!; - ///If [callback] generated an exception then we should not cache data - ///And propagate exception to the place from where [fetch] is triggered - } catch (error) { - rethrow; - } - } else { - return _cachedValueFuture!; - } + return _cachedValueFuture ??= callback().then((value) { + _startStaleTimer(); + return value; + }, onError: (Object error, StackTrace stack) { + invalidate(); + throw error; + }); } } From 4dbb57a16910c7302ad94fa1273181745aab8ddb Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Thu, 21 Dec 2023 01:00:53 +0530 Subject: [PATCH 11/14] fix: fixed tests --- test/async_cache_test.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/async_cache_test.dart b/test/async_cache_test.dart index f83388f..c1da0d7 100644 --- a/test/async_cache_test.dart +++ b/test/async_cache_test.dart @@ -19,11 +19,18 @@ void main() { }); test('should not fetch when callback throws exception', () async { + + cache = AsyncCache(const Duration(hours: 1), cacheErrors: false); + Future asyncFunctionThatThrows() { throw Exception(); } - var cacheFuture = cache.fetch(asyncFunctionThatThrows); - await expectLater(cacheFuture,throwsA(isException)); + + 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 { From d6a8796dbd4fa4720a9b68a509e711a141657e5d Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Thu, 21 Dec 2023 01:11:13 +0530 Subject: [PATCH 12/14] fix: removed comments from top --- lib/src/async_cache.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index c92a733..84f9eaa 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -7,9 +7,7 @@ import 'dart:async'; import '../async.dart'; /// Runs asynchronous functions and caches the result for a period of time. -/// If you doesn't want to cache Exception then you can set -// [_canCacheException] to false. -// +/// /// This class exists to cover the pattern of having potentially expensive code /// such as file I/O, network access, or isolate computation that's unlikely to /// change quickly run fewer times. For example: From 5d18dd257f570cc06506c9a5dd84c709cc2e9511 Mon Sep 17 00:00:00 2001 From: Abhishak Kumar Malviya <57094659+akmalviya03@users.noreply.github.com> Date: Wed, 24 Apr 2024 00:24:53 +0530 Subject: [PATCH 13/14] Added cacheErrors documentation suggestion Co-authored-by: Nate Bosch --- lib/src/async_cache.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/async_cache.dart b/lib/src/async_cache.dart index 84f9eaa..b83df3a 100644 --- a/lib/src/async_cache.dart +++ b/lib/src/async_cache.dart @@ -53,6 +53,8 @@ class AsyncCache { /// The [duration] starts counting after the Future returned by [fetch] /// completes, or after the Stream returned by `fetchStream` emits a done /// event. + /// 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}) : _duration = duration, _cacheErrors = cacheErrors; From de2067d71d1eb9cd26fbfe21325ae9cf909283ab Mon Sep 17 00:00:00 2001 From: akmalviya03 Date: Thu, 25 Apr 2024 07:33:51 +0530 Subject: [PATCH 14/14] fix: formatted async_cache_test.dart --- test/async_cache_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/async_cache_test.dart b/test/async_cache_test.dart index 4a174ce..8af090d 100644 --- a/test/async_cache_test.dart +++ b/test/async_cache_test.dart @@ -19,7 +19,6 @@ void main() { }); test('should not fetch when callback throws exception', () async { - cache = AsyncCache(const Duration(hours: 1), cacheErrors: false); Future asyncFunctionThatThrows() {