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, () { |