ErrorGroup class
An ErrorGroup entangles the errors of multiple Futures and Streams with one another. This allows APIs to expose multiple Futures and Streams that have identical error conditions without forcing API consumers to attach error handling to objects they don't care about.
To use an ErrorGroup, register Futures and Streams with it using registerFuture and registerStream. These methods return wrapped versions of the Futures and Streams, which should then be used in place of the originals. For example:
var errorGroup = new ErrorGroup();
future = errorGroup.registerFuture(future);
stream = errorGroup.registerStream(stream);
An ErrorGroup has two major effects on its wrapped members:
-
An error in any member of the group will be propagated to every member that hasn't already completed. If those members later complete, their values will be ignored.
-
If any member of this group has a listener, errors on members without listeners won't get passed to the top-level error handler.
class ErrorGroup { /// The [Future]s that are members of [this]. final _futures = <_ErrorGroupFuture>[]; /// The [Stream]s that are members of [this]. final _streams = <_ErrorGroupStream>[]; /// Whether [this] has completed, either successfully or with an error. var _isDone = false; /// The [Completer] for [done]. final _doneCompleter = new Completer(); /// The underlying [Future] for [done]. We need to be able to access it /// internally as an [_ErrorGroupFuture] so we can check if it has listeners /// and signal errors on it. _ErrorGroupFuture _done; /// Returns a [Future] that completes successully when all members of [this] /// are complete, or with an error if any member receives an error. /// /// This [Future] is effectively in the group in that an error on it won't be /// passed to the top-level error handler unless no members of the group have /// listeners attached. Future get done => _done; /// Creates a new group with no members. ErrorGroup() { this._done = new _ErrorGroupFuture(this, _doneCompleter.future); } /// Registers a [Future] as a member of [this]. Returns a wrapped version of /// [future] that should be used in its place. /// /// If all members of [this] have already completed successfully or with an /// error, it's a [StateError] to try to register a new [Future]. Future registerFuture(Future future) { if (_isDone) { throw new StateError("Can't register new members on a complete " "ErrorGroup."); } var wrapped = new _ErrorGroupFuture(this, future); _futures.add(wrapped); return wrapped; } /// Registers a [Stream] as a member of [this]. Returns a wrapped version of /// [stream] that should be used in its place. The returned [Stream] will be /// multi-subscription if and only if [stream] is. /// /// Since all errors in a group are passed to all members, the returned /// [Stream] will automatically unsubscribe all its listeners when it /// encounters an error. /// /// If all members of [this] have already completed successfully or with an /// error, it's a [StateError] to try to register a new [Stream]. Stream registerStream(Stream stream) { if (_isDone) { throw new StateError("Can't register new members on a complete " "ErrorGroup."); } var wrapped = new _ErrorGroupStream(this, stream); _streams.add(wrapped); return wrapped; } /// Sends [error] to all members of [this]. Like errors that come from /// members, this will only be passed to the top-level error handler if no /// members have listeners. /// /// If all members of [this] have already completed successfully or with an /// error, it's a [StateError] to try to signal an error. void signalError(var error) { if (_isDone) { throw new StateError("Can't signal errors on a complete ErrorGroup."); } _signalError(error); } /// Signal an error internally. This is just like [signalError], but instead /// of throwing an error if [this] is complete, it just does nothing. void _signalError(var error) { if (_isDone) return; var caught = false; for (var future in _futures) { if (future._isDone || future._hasListeners) caught = true; future._signalError(error); } for (var stream in _streams) { if (stream._isDone || stream._hasListeners) caught = true; stream._signalError(error); } _isDone = true; _done._signalError(error); if (!caught && !_done._hasListeners) error.throwDelayed(); } /// Notifies [this] that one of its member [Future]s is complete. void _signalFutureComplete(_ErrorGroupFuture future) { if (_isDone) return; _isDone = _futures.every((future) => future._isDone) && _streams.every((stream) => stream._isDone); if (_isDone) _doneCompleter.complete(); } /// Notifies [this] that one of its member [Stream]s is complete. void _signalStreamComplete(_ErrorGroupStream stream) { if (_isDone) return; _isDone = _futures.every((future) => future._isDone) && _streams.every((stream) => stream._isDone); if (_isDone) _doneCompleter.complete(); } }
Constructors
new ErrorGroup() #
Creates a new group with no members.
ErrorGroup() { this._done = new _ErrorGroupFuture(this, _doneCompleter.future); }
Properties
final Future done #
Returns a Future that completes successully when all members of this
are complete, or with an error if any member receives an error.
This Future is effectively in the group in that an error on it won't be passed to the top-level error handler unless no members of the group have listeners attached.
Future get done => _done;
Methods
Future registerFuture(Future future) #
Registers a Future as a member of this
. Returns a wrapped version of
future that should be used in its place.
If all members of this
have already completed successfully or with an
error, it's a StateError to try to register a new Future.
Future registerFuture(Future future) { if (_isDone) { throw new StateError("Can't register new members on a complete " "ErrorGroup."); } var wrapped = new _ErrorGroupFuture(this, future); _futures.add(wrapped); return wrapped; }
Stream registerStream(Stream stream) #
Registers a Stream as a member of this
. Returns a wrapped version of
stream that should be used in its place. The returned Stream will be
multi-subscription if and only if
stream is.
Since all errors in a group are passed to all members, the returned Stream will automatically unsubscribe all its listeners when it encounters an error.
If all members of this
have already completed successfully or with an
error, it's a StateError to try to register a new Stream.
Stream registerStream(Stream stream) { if (_isDone) { throw new StateError("Can't register new members on a complete " "ErrorGroup."); } var wrapped = new _ErrorGroupStream(this, stream); _streams.add(wrapped); return wrapped; }
void signalError(error) #
Sends
error to all members of this
. Like errors that come from
members, this will only be passed to the top-level error handler if no
members have listeners.
If all members of this
have already completed successfully or with an
error, it's a StateError to try to signal an error.
void signalError(var error) { if (_isDone) { throw new StateError("Can't signal errors on a complete ErrorGroup."); } _signalError(error); }