|
import 'dart:async'; |
|
|
|
|
|
main() { |
|
var completer = new SafeCompleter(); |
|
completer.complete('foo'); |
|
completer.getFuture().then((val) => print('Complete! $val')); |
|
} |
|
|
|
class SafeCompleter<T> { |
|
bool _getFutureCalled = false; |
|
Completer<T> _completer = new Completer<T>(); |
|
|
|
bool get isCompleted => _completer == null ? true : _completer.isCompleted; |
|
|
|
void complete([FutureOr<T> value]) { |
|
if (_completer == null) { |
|
throw new StateError('Future already completed'); |
|
} |
|
_completer.complete(value); |
|
} |
|
|
|
void completeError(Object error, [StackTrace stackTrace]) { |
|
if (_completer == null) { |
|
throw new StateError('Future already completed'); |
|
} |
|
_completer.completeError(error, stackTrace); |
|
} |
|
|
|
/// Returns the future that will contain the result provided to this completer. |
|
/// |
|
/// This method can only be called once for this instance; subsequent calls will throw a [StateError]. |
|
/// |
|
/// This restriction prevents this completer from leaking the completed value or errors, |
|
/// while still allowing users to check [isCompleted]. |
|
Future<T> getFuture() { |
|
if (_getFutureCalled) { |
|
throw new StateError('getFuture can only be called once; the Future is no longer available'); |
|
} |
|
_getFutureCalled = true; |
|
|
|
_completer.future.then((_) { |
|
_completer = null; |
|
}); |
|
|
|
// TODO: this may be annoying in practice, but would help prevent leaks caused by getFuture never being called. |
|
// TODO: Is there a better way to check this? Is it even necessary to protect against this case? |
|
// This does not cause issues if `.getFuture()` is called after `.complete()` |
|
// but within the same tick, due to the async nature of `.complete`. |
|
assert(_completer == null || !_completer.isCompleted, |
|
'getFuture must be called before the completer completes'); |
|
|
|
return _completer.future; |
|
} |
|
} |