| Index: packages/async/lib/src/async_cache.dart
|
| diff --git a/packages/async/lib/src/async_cache.dart b/packages/async/lib/src/async_cache.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..af52cd06cbccb3d9f99f41bff9b73b2b9cb4ab65
|
| --- /dev/null
|
| +++ b/packages/async/lib/src/async_cache.dart
|
| @@ -0,0 +1,103 @@
|
| +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +import 'dart:async';
|
| +
|
| +import 'package:async/async.dart';
|
| +
|
| +/// Runs asynchronous functions and caches the result for a period of time.
|
| +///
|
| +/// 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:
|
| +///
|
| +/// ```dart
|
| +/// final _usersCache = new AsyncCache<List<String>>(const Duration(hours: 1));
|
| +///
|
| +/// /// Uses the cache if it exists, otherwise calls the closure:
|
| +/// Future<List<String>> get onlineUsers => _usersCache.fetch(() {
|
| +/// // Actually fetch online users here.
|
| +/// });
|
| +/// ```
|
| +///
|
| +/// This class's timing can be mocked using [`fake_async`][fake_async].
|
| +///
|
| +/// [fake_async]: https://pub.dartlang.org/packages/fake_async
|
| +class AsyncCache<T> {
|
| + /// How long cached values stay fresh.
|
| + final Duration _duration;
|
| +
|
| + /// Cached results of a previous [fetchStream] call.
|
| + StreamSplitter<T> _cachedStreamSplitter;
|
| +
|
| + /// Cached results of a previous [fetch] call.
|
| + Future<T> _cachedValueFuture;
|
| +
|
| + /// Fires when the cache should be considered stale.
|
| + Timer _stale;
|
| +
|
| + /// 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.
|
| + factory AsyncCache.ephemeral() => new AsyncCache(Duration.ZERO);
|
| +
|
| + /// Creates a cache that invalidates its contents after [duration] has passed.
|
| + ///
|
| + /// The [duration] starts counting after the Future returned by [fetch]
|
| + /// completes, or after the Stream returned by [fetchStream] emits a done
|
| + /// event.
|
| + AsyncCache(this._duration);
|
| +
|
| + /// Returns a cached value from a previous call to [fetch], or runs [callback]
|
| + /// to compute a new one.
|
| + ///
|
| + /// If [fetch] has been called recently enough, returns its previous return
|
| + /// value. Otherwise, runs [callback] and returns its new return value.
|
| + Future<T> fetch(Future<T> callback()) async {
|
| + if (_cachedStreamSplitter != null) {
|
| + throw new StateError('Previously used to cache via `fetchStream`');
|
| + }
|
| + if (_cachedValueFuture == null) {
|
| + _cachedValueFuture = callback();
|
| + await _cachedValueFuture;
|
| + _startStaleTimer();
|
| + }
|
| + return _cachedValueFuture;
|
| + }
|
| +
|
| + /// Returns a cached stream from a previous call to [fetchStream], or runs
|
| + /// [callback] to compute a new stream.
|
| + ///
|
| + /// If [fetchStream] has been called recently enough, returns a copy of its
|
| + /// previous return value. Otherwise, runs [callback] and returns its new
|
| + /// return value.
|
| + Stream<T> fetchStream(Stream<T> callback()) {
|
| + if (_cachedValueFuture != null) {
|
| + throw new StateError('Previously used to cache via `fetch`');
|
| + }
|
| + if (_cachedStreamSplitter == null) {
|
| + _cachedStreamSplitter = new StreamSplitter(callback()
|
| + .transform(new StreamTransformer.fromHandlers(handleDone: (sink) {
|
| + _startStaleTimer();
|
| + sink.close();
|
| + })));
|
| + }
|
| + return _cachedStreamSplitter.split();
|
| + }
|
| +
|
| + /// Removes any cached value.
|
| + void invalidate() {
|
| + _cachedValueFuture = null;
|
| + _cachedStreamSplitter?.close();
|
| + _cachedStreamSplitter = null;
|
| + _stale?.cancel();
|
| + _stale = null;
|
| + }
|
| +
|
| + void _startStaleTimer() {
|
| + _stale = new Timer(_duration, invalidate);
|
| + }
|
| +}
|
|
|