blob: 8b80143ac88a039b894c70fa4052148615f602a5 [file] [log] [blame]
part of '../async.dart';
enum FutureStatus { pending, rejected, fulfilled }
class FutureResult<T> {
FutureResult(ReactiveContext context, Future<T> future, dynamic initialResult,
FutureStatus initialStatus, String name)
: _axnController =
ActionController(context: context, name: '$name.ActionController'),
_status = Observable(initialStatus, name: '$name.status'),
_result = Observable<dynamic>(initialResult, name: '$name.result') {
future.then(_fulfill, onError: _reject);
}
final ActionController _axnController;
final Observable<FutureStatus> _status;
FutureStatus get status => _status.value;
final Observable _result;
dynamic get result => _result.value;
void _fulfill(T value) {
final prevDerivation = _axnController.startAction();
try {
_status.value = FutureStatus.fulfilled;
_result.value = value;
} finally {
_axnController.endAction(prevDerivation);
}
}
void _reject(dynamic error) {
final prevDerivation = _axnController.startAction();
try {
_status.value = FutureStatus.rejected;
_result.value = error;
} finally {
_axnController.endAction(prevDerivation);
}
}
}
class ObservableFuture<T> implements Future<T>, ObservableValue<T?> {
/// Create a new observable future that tracks the state of the provided future.
ObservableFuture(Future<T> future, {ReactiveContext? context, String? name})
: this._(
context ?? mainContext, future, FutureStatus.pending, null, name);
/// Create a new future that is completed with a value.
///
/// [status] is immediately [FutureStatus.fulfilled].
ObservableFuture.value(T value, {ReactiveContext? context, String? name})
: this._(context ?? mainContext, Future.value(value),
FutureStatus.fulfilled, value, name);
/// Create a new future that is completed with an error.
///
/// [status] is immediately [FutureStatus.rejected].
ObservableFuture.error(Object error, {ReactiveContext? context, String? name})
: this._(context ?? mainContext, Future.error(error),
FutureStatus.rejected, error, name);
ObservableFuture._(this._context, this._future, FutureStatus initialStatus,
dynamic initialResult, String? name) {
_name = name ?? _context.nameFor('ObservableFuture<$T>');
// create the result up-front instead of being lazy
_result =
FutureResult(_context, _future, initialResult, initialStatus, _name);
}
final ReactiveContext _context;
Future<T> _future;
late FutureResult<T> _result;
late String _name;
String get name => _name;
/// Observable status of this.
FutureStatus get status => _result.status;
/// Value if this completed with a value.
///
/// Null otherwise.
@override
T? get value => status == FutureStatus.fulfilled ? _result.result as T : null;
/// Error value if this completed with an error
///
/// Null otherwise.
dynamic get error => status == FutureStatus.rejected ? _result.result : null;
/// Error or value of this.
///
/// Null if this hasn't yet completed.
dynamic get result => _result.result;
/// Maps the current state of this.
///
/// Returns null if a handler for the current state is not provided.
R? match<R>(
{R Function(T)? fulfilled,
// ignore:avoid_annotating_with_dynamic
R Function(dynamic)? rejected,
R Function()? pending}) {
final status = this.status;
if (status == FutureStatus.fulfilled) {
return fulfilled == null ? null : fulfilled(result as T);
} else if (status == FutureStatus.rejected) {
return rejected == null ? null : rejected(result);
}
return pending == null ? null : pending();
}
/// Returns a new future that starts with the [status] and [result] of this.
///
/// The [status] and [result] changes when the provided future completes.
/// Useful when you don't want to clear the result of the previous operation while
/// executing the new operation.
ObservableFuture<T> replace(Future<T> nextFuture) =>
ObservableFuture<T>._(_context, nextFuture, status, result, name);
@override
ObservableStream<T> asStream() => ObservableStream._(
_context, _future.asStream(), value, false, '${name}_asStream');
@override
ObservableFuture<T> catchError(Function onError,
{bool Function(Object error)? test}) =>
ObservableFuture._(_context, _future.catchError(onError, test: test),
FutureStatus.pending, null, name);
@override
ObservableFuture<R> then<R>(FutureOr<R> Function(T value) onValue,
{Function? onError}) =>
ObservableFuture._(_context, _future.then(onValue, onError: onError),
FutureStatus.pending, null, name);
@override
ObservableFuture<T> timeout(Duration timeLimit,
{FutureOr<T> Function()? onTimeout}) =>
ObservableFuture._(
_context,
_future.timeout(timeLimit, onTimeout: onTimeout),
FutureStatus.pending,
null,
name);
@override
ObservableFuture<T> whenComplete(FutureOr Function() action) =>
ObservableFuture._(_context, _future.whenComplete(action),
FutureStatus.pending, null, name);
}