| Index: tool/input_sdk/lib/async/future_impl.dart
|
| diff --git a/tool/input_sdk/lib/async/future_impl.dart b/tool/input_sdk/lib/async/future_impl.dart
|
| index 1c6bf545e9e84e953f5d189001a20f04e9057e97..eb4d6cfcaaf090a9c862cdaabeac192afdff1a69 100644
|
| --- a/tool/input_sdk/lib/async/future_impl.dart
|
| +++ b/tool/input_sdk/lib/async/future_impl.dart
|
| @@ -5,7 +5,7 @@
|
| part of dart.async;
|
|
|
| /** The onValue and onError handlers return either a value or a future */
|
| -typedef dynamic _FutureOnValue<T>(T value);
|
| +typedef dynamic/*T|Future<T>*/ _FutureOnValue<S, T>(S value);
|
| /** Test used by [Future.catchError] to handle skip some errors. */
|
| typedef bool _FutureErrorTest(var error);
|
| /** Used by [WhenFuture]. */
|
| @@ -57,21 +57,23 @@ class _SyncCompleter<T> extends _Completer<T> {
|
| }
|
| }
|
|
|
| -class _FutureListener {
|
| +class _FutureListener<S, T> {
|
| static const int MASK_VALUE = 1;
|
| static const int MASK_ERROR = 2;
|
| static const int MASK_TEST_ERROR = 4;
|
| static const int MASK_WHENCOMPLETE = 8;
|
| static const int STATE_CHAIN = 0;
|
| static const int STATE_THEN = MASK_VALUE;
|
| - static const int STATE_THEN_ONERROR = MASK_VALUE | MASK_ERROR;
|
| + // TODO(johnmccutchan): Remove the hard coded value. See #26030.
|
| + static const int STATE_THEN_ONERROR = 3; // MASK_VALUE | MASK_ERROR.
|
| static const int STATE_CATCHERROR = MASK_ERROR;
|
| - static const int STATE_CATCHERROR_TEST = MASK_ERROR | MASK_TEST_ERROR;
|
| + // TODO(johnmccutchan): Remove the hard coded value. See #26030.
|
| + static const int STATE_CATCHERROR_TEST = 6; // MASK_ERROR | MASK_TEST_ERROR.
|
| static const int STATE_WHENCOMPLETE = MASK_WHENCOMPLETE;
|
| // Listeners on the same future are linked through this link.
|
| _FutureListener _nextListener = null;
|
| // The future to complete when this listener is activated.
|
| - final _Future result;
|
| + final _Future<T> result;
|
| // Which fields means what.
|
| final int state;
|
| // Used for then/whenDone callback and error test
|
| @@ -80,7 +82,7 @@ class _FutureListener {
|
| final Function errorCallback;
|
|
|
| _FutureListener.then(this.result,
|
| - _FutureOnValue onValue, Function errorCallback)
|
| + _FutureOnValue<S, T> onValue, Function errorCallback)
|
| : callback = onValue,
|
| errorCallback = errorCallback,
|
| state = (errorCallback == null) ? STATE_THEN : STATE_THEN_ONERROR;
|
| @@ -95,11 +97,6 @@ class _FutureListener {
|
| errorCallback = null,
|
| state = STATE_WHENCOMPLETE;
|
|
|
| - _FutureListener.chain(this.result)
|
| - : callback = null,
|
| - errorCallback = null,
|
| - state = STATE_CHAIN;
|
| -
|
| Zone get _zone => result._zone;
|
|
|
| bool get handlesValue => (state & MASK_VALUE != 0);
|
| @@ -107,18 +104,57 @@ class _FutureListener {
|
| bool get hasErrorTest => (state == STATE_CATCHERROR_TEST);
|
| bool get handlesComplete => (state == STATE_WHENCOMPLETE);
|
|
|
| - _FutureOnValue get _onValue {
|
| +
|
| + _FutureOnValue<S, T> get _onValue {
|
| assert(handlesValue);
|
| - return callback;
|
| + return callback as Object /*=_FutureOnValue<S, T>*/;
|
| }
|
| Function get _onError => errorCallback;
|
| _FutureErrorTest get _errorTest {
|
| assert(hasErrorTest);
|
| - return callback;
|
| + return callback as Object /*=_FutureErrorTest*/;
|
| }
|
| _FutureAction get _whenCompleteAction {
|
| assert(handlesComplete);
|
| - return callback;
|
| + return callback as Object /*=_FutureAction*/;
|
| + }
|
| +
|
| + /// Whether this listener has an error callback.
|
| + ///
|
| + /// This function must only be called if the listener [handlesError].
|
| + bool get hasErrorCallback {
|
| + assert(handlesError);
|
| + return _onError != null;
|
| + }
|
| +
|
| + dynamic/*T|Future<T>*/ handleValue(S sourceResult) {
|
| + return _zone.runUnary/*<dynamic/*T|Future<T>*/, S>*/(
|
| + _onValue, sourceResult);
|
| + }
|
| +
|
| + bool matchesErrorTest(AsyncError asyncError) {
|
| + if (!hasErrorTest) return true;
|
| + _FutureErrorTest test = _errorTest;
|
| + return _zone.runUnary/*<bool, dynamic>*/(_errorTest, asyncError.error);
|
| + }
|
| +
|
| + dynamic/*T|Future<T>*/ handleError(AsyncError asyncError) {
|
| + assert(handlesError && hasErrorCallback);
|
| + if (errorCallback is ZoneBinaryCallback) {
|
| + var typedErrorCallback = errorCallback as Object
|
| + /*=ZoneBinaryCallback<Object/*T|Future<T>*/, Object, StackTrace>*/;
|
| + return _zone.runBinary(typedErrorCallback,
|
| + asyncError.error,
|
| + asyncError.stackTrace);
|
| + } else {
|
| + return _zone.runUnary/*<dynamic/*T|Future<T>*/, dynamic>*/(
|
| + errorCallback, asyncError.error);
|
| + }
|
| + }
|
| +
|
| + dynamic handleWhenComplete() {
|
| + assert(!handlesError);
|
| + return _zone.run(_whenCompleteAction);
|
| }
|
| }
|
|
|
| @@ -133,8 +169,7 @@ class _Future<T> implements Future<T> {
|
| static const int _PENDING_COMPLETE = 1;
|
| /// The future has been chained to another future. The result of that
|
| /// other future becomes the result of this future as well.
|
| - // TODO(floitsch): we don't really need a special "_CHAINED" state. We could
|
| - // just use the PENDING_COMPLETE state instead.
|
| + /// [resultOrListeners] contains the source future.
|
| static const int _CHAINED = 2;
|
| /// The future has been completed with a value result.
|
| static const int _VALUE = 4;
|
| @@ -167,14 +202,10 @@ class _Future<T> implements Future<T> {
|
| * is waiting for the other future to complete, and when it does, this future
|
| * will complete with the same result.
|
| * All listeners are forwarded to the other future.
|
| - *
|
| - * The cases are disjoint - incomplete and unchained ([_INCOMPLETE]),
|
| - * incomplete and chained ([_CHAINED]), or completed with value or error
|
| - * ([_VALUE] or [_ERROR]) - so the field only needs to hold
|
| - * one value at a time.
|
| */
|
| var _resultOrListeners;
|
|
|
| + // This constructor is used by async/await.
|
| _Future();
|
|
|
| /// Valid types for value: `T` or `Future<T>`.
|
| @@ -186,70 +217,77 @@ class _Future<T> implements Future<T> {
|
| _asyncCompleteError(error, stackTrace);
|
| }
|
|
|
| - bool get _mayComplete => _state == _INCOMPLETE;
|
| - bool get _isChained => _state == _CHAINED;
|
| - bool get _isComplete => _state >= _VALUE;
|
| - bool get _hasValue => _state == _VALUE;
|
| - bool get _hasError => _state == _ERROR;
|
| -
|
| - set _isChained(bool value) {
|
| - if (value) {
|
| - assert(!_isComplete);
|
| - _state = _CHAINED;
|
| - } else {
|
| - assert(_isChained);
|
| - _state = _INCOMPLETE;
|
| - }
|
| + bool get _mayComplete => _state == _INCOMPLETE;
|
| + bool get _isPendingComplete => _state == _PENDING_COMPLETE;
|
| + bool get _mayAddListener => _state <= _PENDING_COMPLETE;
|
| + bool get _isChained => _state == _CHAINED;
|
| + bool get _isComplete => _state >= _VALUE;
|
| + bool get _hasError => _state == _ERROR;
|
| +
|
| + void _setChained(_Future source) {
|
| + assert(_mayAddListener);
|
| + _state = _CHAINED;
|
| + _resultOrListeners = source;
|
| }
|
|
|
| - Future/*<S>*/ then/*<S>*/(/*=S*/ f(T value), { Function onError }) {
|
| - _Future/*<S>*/ result = new _Future();
|
| - if (!identical(result._zone, _ROOT_ZONE)) {
|
| - f = result._zone.registerUnaryCallback(f);
|
| + Future/*<E>*/ then/*<E>*/(
|
| + /*=dynamic/*E|Future<E>*/*/ f(T value), { Function onError }) {
|
| + Zone currentZone = Zone.current;
|
| + ZoneUnaryCallback registered;
|
| + if (!identical(currentZone, _ROOT_ZONE)) {
|
| + f = currentZone.registerUnaryCallback/*<dynamic, T>*/(f);
|
| if (onError != null) {
|
| - onError = _registerErrorHandler(onError, result._zone);
|
| + onError = _registerErrorHandler/*<T>*/(onError, currentZone);
|
| }
|
| }
|
| - _addListener(new _FutureListener.then(result, f, onError));
|
| + return _thenNoZoneRegistration/*<E>*/(f, onError);
|
| + }
|
| +
|
| + // This method is used by async/await.
|
| + Future/*<E>*/ _thenNoZoneRegistration/*<E>*/(f(T value), Function onError) {
|
| + _Future/*<E>*/ result = new _Future/*<E>*/();
|
| + _addListener(new _FutureListener/*<T, E>*/.then(result, f, onError));
|
| return result;
|
| }
|
|
|
| - Future catchError(Function onError, { bool test(error) }) {
|
| - _Future result = new _Future();
|
| + Future/*<T>*/ catchError(Function onError, { bool test(error) }) {
|
| + _Future/*<T>*/ result = new _Future/*<T>*/();
|
| if (!identical(result._zone, _ROOT_ZONE)) {
|
| - onError = _registerErrorHandler(onError, result._zone);
|
| + onError = _registerErrorHandler/*<T>*/(onError, result._zone);
|
| if (test != null) test = result._zone.registerUnaryCallback(test);
|
| }
|
| - _addListener(new _FutureListener.catchError(result, onError, test));
|
| + _addListener(new _FutureListener/*<T, T>*/.catchError(
|
| + result, onError, test));
|
| return result;
|
| }
|
|
|
| Future<T> whenComplete(action()) {
|
| - _Future result = new _Future<T>();
|
| + _Future<T> result = new _Future<T>();
|
| if (!identical(result._zone, _ROOT_ZONE)) {
|
| - action = result._zone.registerCallback(action);
|
| + action = result._zone.registerCallback/*<dynamic>*/(action);
|
| }
|
| - _addListener(new _FutureListener.whenComplete(result, action));
|
| + _addListener(new _FutureListener/*<T, T>*/.whenComplete(result, action));
|
| return result;
|
| }
|
|
|
| - Stream<T> asStream() => new Stream.fromFuture(this);
|
| + Stream<T> asStream() => new Stream<T>.fromFuture(this);
|
|
|
| - void _markPendingCompletion() {
|
| - if (!_mayComplete) throw new StateError("Future already completed");
|
| + void _setPendingComplete() {
|
| + assert(_mayComplete);
|
| _state = _PENDING_COMPLETE;
|
| }
|
|
|
| - T get _value {
|
| - assert(_isComplete && _hasValue);
|
| + AsyncError get _error {
|
| + assert(_hasError);
|
| return _resultOrListeners;
|
| }
|
|
|
| - AsyncError get _error {
|
| - assert(_isComplete && _hasError);
|
| + _Future get _chainSource {
|
| + assert(_isChained);
|
| return _resultOrListeners;
|
| }
|
|
|
| + // This method is used by async/await.
|
| void _setValue(T value) {
|
| assert(!_isComplete); // But may have a completion pending.
|
| _state = _VALUE;
|
| @@ -266,16 +304,70 @@ class _Future<T> implements Future<T> {
|
| _setErrorObject(new AsyncError(error, stackTrace));
|
| }
|
|
|
| + /// Copy the completion result of [source] into this future.
|
| + ///
|
| + /// Used when a chained future notices that its source is completed.
|
| + void _cloneResult(_Future source) {
|
| + assert(!_isComplete);
|
| + assert(source._isComplete);
|
| + _state = source._state;
|
| + _resultOrListeners = source._resultOrListeners;
|
| + }
|
| +
|
| void _addListener(_FutureListener listener) {
|
| assert(listener._nextListener == null);
|
| - if (_isComplete) {
|
| + if (_mayAddListener) {
|
| + listener._nextListener = _resultOrListeners;
|
| + _resultOrListeners = listener;
|
| + } else {
|
| + if (_isChained) {
|
| + // Delegate listeners to chained source future.
|
| + // If the source is complete, instead copy its values and
|
| + // drop the chaining.
|
| + _Future source = _chainSource;
|
| + if (!source._isComplete) {
|
| + source._addListener(listener);
|
| + return;
|
| + }
|
| + _cloneResult(source);
|
| + }
|
| + assert(_isComplete);
|
| // Handle late listeners asynchronously.
|
| _zone.scheduleMicrotask(() {
|
| _propagateToListeners(this, listener);
|
| });
|
| + }
|
| + }
|
| +
|
| + void _prependListeners(_FutureListener listeners) {
|
| + if (listeners == null) return;
|
| + if (_mayAddListener) {
|
| + _FutureListener existingListeners = _resultOrListeners;
|
| + _resultOrListeners = listeners;
|
| + if (existingListeners != null) {
|
| + _FutureListener cursor = listeners;
|
| + while (cursor._nextListener != null) {
|
| + cursor = cursor._nextListener;
|
| + }
|
| + cursor._nextListener = existingListeners;
|
| + }
|
| } else {
|
| - listener._nextListener = _resultOrListeners;
|
| - _resultOrListeners = listener;
|
| + if (_isChained) {
|
| + // Delegate listeners to chained source future.
|
| + // If the source is complete, instead copy its values and
|
| + // drop the chaining.
|
| + _Future source = _chainSource;
|
| + if (!source._isComplete) {
|
| + source._prependListeners(listeners);
|
| + return;
|
| + }
|
| + _cloneResult(source);
|
| + }
|
| + assert(_isComplete);
|
| + listeners = _reverseListeners(listeners);
|
| + _zone.scheduleMicrotask(() {
|
| + _propagateToListeners(this, listeners);
|
| + });
|
| }
|
| }
|
|
|
| @@ -285,7 +377,12 @@ class _Future<T> implements Future<T> {
|
| assert(!_isComplete);
|
| _FutureListener current = _resultOrListeners;
|
| _resultOrListeners = null;
|
| + return _reverseListeners(current);
|
| + }
|
| +
|
| + _FutureListener _reverseListeners(_FutureListener listeners) {
|
| _FutureListener prev = null;
|
| + _FutureListener current = listeners;
|
| while (current != null) {
|
| _FutureListener next = current._nextListener;
|
| current._nextListener = prev;
|
| @@ -296,41 +393,54 @@ class _Future<T> implements Future<T> {
|
| }
|
|
|
| // Take the value (when completed) of source and complete target with that
|
| - // value (or error). This function can chain all Futures, but is slower
|
| - // for _Future than _chainCoreFuture - Use _chainCoreFuture in that case.
|
| + // value (or error). This function could chain all Futures, but is slower
|
| + // for _Future than _chainCoreFuture, so you must use _chainCoreFuture
|
| + // in that case.
|
| static void _chainForeignFuture(Future source, _Future target) {
|
| assert(!target._isComplete);
|
| assert(source is! _Future);
|
|
|
| // Mark the target as chained (and as such half-completed).
|
| - target._isChained = true;
|
| - source.then((value) {
|
| - assert(target._isChained);
|
| - target._completeWithValue(value);
|
| - },
|
| - // TODO(floitsch): eventually we would like to make this non-optional
|
| - // and dependent on the listeners of the target future. If none of
|
| - // the target future's listeners want to have the stack trace we don't
|
| - // need a trace.
|
| - onError: (error, [stackTrace]) {
|
| - assert(target._isChained);
|
| - target._completeError(error, stackTrace);
|
| + target._setPendingComplete();
|
| + try {
|
| + source.then((value) {
|
| + assert(target._isPendingComplete);
|
| + target._completeWithValue(value);
|
| + },
|
| + // TODO(floitsch): eventually we would like to make this non-optional
|
| + // and dependent on the listeners of the target future. If none of
|
| + // the target future's listeners want to have the stack trace we don't
|
| + // need a trace.
|
| + onError: (error, [stackTrace]) {
|
| + assert(target._isPendingComplete);
|
| + target._completeError(error, stackTrace);
|
| + });
|
| + } catch (e, s) {
|
| + // This only happens if the `then` call threw synchronously when given
|
| + // valid arguments.
|
| + // That requires a non-conforming implementation of the Future interface,
|
| + // which should, hopefully, never happen.
|
| + scheduleMicrotask(() {
|
| + target._completeError(e, s);
|
| });
|
| + }
|
| }
|
|
|
| // Take the value (when completed) of source and complete target with that
|
| // value (or error). This function expects that source is a _Future.
|
| static void _chainCoreFuture(_Future source, _Future target) {
|
| - assert(!target._isComplete);
|
| - assert(source is _Future);
|
| -
|
| - // Mark the target as chained (and as such half-completed).
|
| - target._isChained = true;
|
| - _FutureListener listener = new _FutureListener.chain(target);
|
| + assert(target._mayAddListener); // Not completed, not already chained.
|
| + while (source._isChained) {
|
| + source = source._chainSource;
|
| + }
|
| if (source._isComplete) {
|
| - _propagateToListeners(source, listener);
|
| + _FutureListener listeners = target._removeListeners();
|
| + target._cloneResult(source);
|
| + _propagateToListeners(target, listeners);
|
| } else {
|
| - source._addListener(listener);
|
| + _FutureListener listeners = target._resultOrListeners;
|
| + target._setChained(source);
|
| + source._prependListeners(listeners);
|
| }
|
| }
|
|
|
| @@ -344,12 +454,12 @@ class _Future<T> implements Future<T> {
|
| }
|
| } else {
|
| _FutureListener listeners = _removeListeners();
|
| - _setValue(value);
|
| + _setValue(value as Object /*=T*/);
|
| _propagateToListeners(this, listeners);
|
| }
|
| }
|
|
|
| - void _completeWithValue(value) {
|
| + void _completeWithValue(T value) {
|
| assert(!_isComplete);
|
| assert(value is! Future);
|
|
|
| @@ -379,17 +489,15 @@ class _Future<T> implements Future<T> {
|
| // unhandled error, even though we know we are already going to listen to
|
| // it.
|
|
|
| - if (value == null) {
|
| - // No checks for `null`.
|
| - } else if (value is Future) {
|
| + if (value is Future) {
|
| // Assign to typed variables so we get earlier checks in checked mode.
|
| - Future<T> typedFuture = value;
|
| + Future<T> typedFuture = value as Object /*=Future<T>*/;
|
| if (typedFuture is _Future) {
|
| _Future<T> coreFuture = typedFuture;
|
| - if (coreFuture._isComplete && coreFuture._hasError) {
|
| + if (coreFuture._hasError) {
|
| // Case 1 from above. Delay completion to enable the user to register
|
| // callbacks.
|
| - _markPendingCompletion();
|
| + _setPendingComplete();
|
| _zone.scheduleMicrotask(() {
|
| _chainCoreFuture(coreFuture, this);
|
| });
|
| @@ -397,26 +505,25 @@ class _Future<T> implements Future<T> {
|
| _chainCoreFuture(coreFuture, this);
|
| }
|
| } else {
|
| - // Case 2 from above. Chain the future immidiately.
|
| + // Case 2 from above. Chain the future immediately.
|
| // Note that we are still completing asynchronously (through
|
| - // _chainForeignFuture)..
|
| + // _chainForeignFuture).
|
| _chainForeignFuture(typedFuture, this);
|
| }
|
| return;
|
| - } else {
|
| - T typedValue = value;
|
| }
|
| + T typedValue = value as Object /*=T*/;
|
|
|
| - _markPendingCompletion();
|
| + _setPendingComplete();
|
| _zone.scheduleMicrotask(() {
|
| - _completeWithValue(value);
|
| + _completeWithValue(typedValue);
|
| });
|
| }
|
|
|
| void _asyncCompleteError(error, StackTrace stackTrace) {
|
| assert(!_isComplete);
|
|
|
| - _markPendingCompletion();
|
| + _setPendingComplete();
|
| _zone.scheduleMicrotask(() {
|
| _completeError(error, stackTrace);
|
| });
|
| @@ -448,22 +555,21 @@ class _Future<T> implements Future<T> {
|
| _propagateToListeners(source, listener);
|
| }
|
| _FutureListener listener = listeners;
|
| + final sourceResult = source._resultOrListeners;
|
| // Do the actual propagation.
|
| - // Set initial state of listenerHasValue and listenerValueOrError. These
|
| - // variables are updated, with the outcome of potential callbacks.
|
| - bool listenerHasValue = true;
|
| - final sourceValue = hasError ? null : source._value;
|
| - var listenerValueOrError = sourceValue;
|
| - // Set to true if a whenComplete needs to wait for a future.
|
| - // The whenComplete action will resume the propagation by itself.
|
| - bool isPropagationAborted = false;
|
| - // TODO(floitsch): mark the listener as pending completion. Currently
|
| - // we can't do this, since the markPendingCompletion verifies that
|
| - // the future is not already marked (or chained).
|
| + // Set initial state of listenerHasError and listenerValueOrError. These
|
| + // variables are updated with the outcome of potential callbacks.
|
| + // Non-error results, including futures, are stored in
|
| + // listenerValueOrError and listenerHasError is set to false. Errors
|
| + // are stored in listenerValueOrError as an [AsyncError] and
|
| + // listenerHasError is set to true.
|
| + bool listenerHasError = hasError;
|
| + var listenerValueOrError = sourceResult;
|
| +
|
| // Only if we either have an error or callbacks, go into this, somewhat
|
| // expensive, branch. Here we'll enter/leave the zone. Many futures
|
| - // doesn't have callbacks, so this is a significant optimization.
|
| - if (hasError || (listener.handlesValue || listener.handlesComplete)) {
|
| + // don't have callbacks, so this is a significant optimization.
|
| + if (hasError || listener.handlesValue || listener.handlesComplete) {
|
| Zone zone = listener._zone;
|
| if (hasError && !source._zone.inSameErrorZone(zone)) {
|
| // Don't cross zone boundaries with errors.
|
| @@ -479,119 +585,99 @@ class _Future<T> implements Future<T> {
|
| oldZone = Zone._enter(zone);
|
| }
|
|
|
| - bool handleValueCallback() {
|
| + // These callbacks are abstracted to isolate the try/catch blocks
|
| + // from the rest of the code to work around a V8 glass jaw.
|
| + void handleWhenCompleteCallback() {
|
| + // The whenComplete-handler is not combined with normal value/error
|
| + // handling. This means at most one handleX method is called per
|
| + // listener.
|
| + assert(!listener.handlesValue);
|
| + assert(!listener.handlesError);
|
| + var completeResult;
|
| try {
|
| - listenerValueOrError = zone.runUnary(listener._onValue,
|
| - sourceValue);
|
| - return true;
|
| + completeResult = listener.handleWhenComplete();
|
| } catch (e, s) {
|
| - listenerValueOrError = new AsyncError(e, s);
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - void handleError() {
|
| - AsyncError asyncError = source._error;
|
| - bool matchesTest = true;
|
| - if (listener.hasErrorTest) {
|
| - _FutureErrorTest test = listener._errorTest;
|
| - try {
|
| - matchesTest = zone.runUnary(test, asyncError.error);
|
| - } catch (e, s) {
|
| - listenerValueOrError = identical(asyncError.error, e) ?
|
| - asyncError : new AsyncError(e, s);
|
| - listenerHasValue = false;
|
| - return;
|
| + if (hasError && identical(source._error.error, e)) {
|
| + listenerValueOrError = source._error;
|
| + } else {
|
| + listenerValueOrError = new AsyncError(e, s);
|
| }
|
| + listenerHasError = true;
|
| + return;
|
| }
|
| - Function errorCallback = listener._onError;
|
| - if (matchesTest && errorCallback != null) {
|
| - try {
|
| - if (errorCallback is ZoneBinaryCallback) {
|
| - listenerValueOrError = zone.runBinary(errorCallback,
|
| - asyncError.error,
|
| - asyncError.stackTrace);
|
| - } else {
|
| - listenerValueOrError = zone.runUnary(errorCallback,
|
| - asyncError.error);
|
| + if (completeResult is Future) {
|
| + if (completeResult is _Future && completeResult._isComplete) {
|
| + if (completeResult._hasError) {
|
| + listenerValueOrError = completeResult._error;
|
| + listenerHasError = true;
|
| }
|
| - } catch (e, s) {
|
| - listenerValueOrError = identical(asyncError.error, e) ?
|
| - asyncError : new AsyncError(e, s);
|
| - listenerHasValue = false;
|
| + // Otherwise use the existing result of source.
|
| return;
|
| }
|
| - listenerHasValue = true;
|
| - } else {
|
| - // Copy over the error from the source.
|
| - listenerValueOrError = asyncError;
|
| - listenerHasValue = false;
|
| + // We have to wait for the completeResult future to complete
|
| + // before knowing if it's an error or we should use the result
|
| + // of source.
|
| + var originalSource = source;
|
| + listenerValueOrError = completeResult.then((_) => originalSource);
|
| + listenerHasError = false;
|
| }
|
| }
|
|
|
| - void handleWhenCompleteCallback() {
|
| - var completeResult;
|
| + void handleValueCallback() {
|
| try {
|
| - completeResult = zone.run(listener._whenCompleteAction);
|
| + listenerValueOrError = listener.handleValue(sourceResult);
|
| } catch (e, s) {
|
| - if (hasError && identical(source._error.error, e)) {
|
| + listenerValueOrError = new AsyncError(e, s);
|
| + listenerHasError = true;
|
| + }
|
| + }
|
| +
|
| + void handleError() {
|
| + try {
|
| + AsyncError asyncError = source._error;
|
| + if (listener.matchesErrorTest(asyncError) &&
|
| + listener.hasErrorCallback) {
|
| + listenerValueOrError = listener.handleError(asyncError);
|
| + listenerHasError = false;
|
| + }
|
| + } catch (e, s) {
|
| + if (identical(source._error.error, e)) {
|
| listenerValueOrError = source._error;
|
| } else {
|
| listenerValueOrError = new AsyncError(e, s);
|
| }
|
| - listenerHasValue = false;
|
| - return;
|
| - }
|
| - if (completeResult is Future) {
|
| - _Future result = listener.result;
|
| - result._isChained = true;
|
| - isPropagationAborted = true;
|
| - completeResult.then((ignored) {
|
| - _propagateToListeners(source, new _FutureListener.chain(result));
|
| - }, onError: (error, [stackTrace]) {
|
| - // When there is an error, we have to make the error the new
|
| - // result of the current listener.
|
| - if (completeResult is! _Future) {
|
| - // This should be a rare case.
|
| - completeResult = new _Future();
|
| - completeResult._setError(error, stackTrace);
|
| - }
|
| - _propagateToListeners(completeResult,
|
| - new _FutureListener.chain(result));
|
| - });
|
| + listenerHasError = true;
|
| }
|
| }
|
|
|
| - if (!hasError) {
|
| +
|
| + if (listener.handlesComplete) {
|
| + handleWhenCompleteCallback();
|
| + } else if (!hasError) {
|
| if (listener.handlesValue) {
|
| - listenerHasValue = handleValueCallback();
|
| + handleValueCallback();
|
| }
|
| } else {
|
| - handleError();
|
| - }
|
| - if (listener.handlesComplete) {
|
| - handleWhenCompleteCallback();
|
| + if (listener.handlesError) {
|
| + handleError();
|
| + }
|
| }
|
| +
|
| // If we changed zone, oldZone will not be null.
|
| if (oldZone != null) Zone._leave(oldZone);
|
|
|
| - if (isPropagationAborted) return;
|
| // If the listener's value is a future we need to chain it. Note that
|
| - // this can only happen if there is a callback. Since 'is' checks
|
| - // can be expensive, we're trying to avoid it.
|
| - if (listenerHasValue &&
|
| - !identical(sourceValue, listenerValueOrError) &&
|
| - listenerValueOrError is Future) {
|
| + // this can only happen if there is a callback.
|
| + if (listenerValueOrError is Future) {
|
| Future chainSource = listenerValueOrError;
|
| // Shortcut if the chain-source is already completed. Just continue
|
| // the loop.
|
| _Future result = listener.result;
|
| if (chainSource is _Future) {
|
| if (chainSource._isComplete) {
|
| - // propagate the value (simulating a tail call).
|
| - result._isChained = true;
|
| + listeners = result._removeListeners();
|
| + result._cloneResult(chainSource);
|
| source = chainSource;
|
| - listeners = new _FutureListener.chain(result);
|
| continue;
|
| } else {
|
| _chainCoreFuture(chainSource, result);
|
| @@ -604,7 +690,7 @@ class _Future<T> implements Future<T> {
|
| }
|
| _Future result = listener.result;
|
| listeners = result._removeListeners();
|
| - if (listenerHasValue) {
|
| + if (!listenerHasError) {
|
| result._setValue(listenerValueOrError);
|
| } else {
|
| AsyncError asyncError = listenerValueOrError;
|
| @@ -615,9 +701,9 @@ class _Future<T> implements Future<T> {
|
| }
|
| }
|
|
|
| - Future timeout(Duration timeLimit, {onTimeout()}) {
|
| + Future<T> timeout(Duration timeLimit, {onTimeout()}) {
|
| if (_isComplete) return new _Future.immediate(this);
|
| - _Future result = new _Future();
|
| + _Future<T> result = new _Future<T>();
|
| Timer timer;
|
| if (onTimeout == null) {
|
| timer = new Timer(timeLimit, () {
|
|
|