Index: sdk/lib/async/future_impl.dart |
diff --git a/sdk/lib/async/future_impl.dart b/sdk/lib/async/future_impl.dart |
index 35dfd1194f052403843fff9fef9fef1ebcaf706e..f27b34dce6c77990bee147b542f7e172ba205870 100644 |
--- a/sdk/lib/async/future_impl.dart |
+++ b/sdk/lib/async/future_impl.dart |
@@ -187,20 +187,16 @@ 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; |
+ bool get _mayComplete => _state == _INCOMPLETE; |
+ bool get _isPendingComplete => _state == _PENDING_COMPLETE; |
+ 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; |
- } |
+ void _setChained() { |
+ assert(!_isComplete); |
+ _state = _CHAINED; |
} |
Future then(f(T value), { Function onError }) { |
@@ -304,14 +300,15 @@ 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; |
+ target._setChained(); |
try { |
source.then((value) { |
assert(target._isChained); |
@@ -343,7 +340,7 @@ class _Future<T> implements Future<T> { |
assert(source is _Future); |
// Mark the target as chained (and as such half-completed). |
- target._isChained = true; |
+ target._setChained(); |
_FutureListener listener = new _FutureListener.chain(target); |
if (source._isComplete) { |
_propagateToListeners(source, listener); |
@@ -404,7 +401,7 @@ class _Future<T> implements Future<T> { |
Future<T> typedFuture = value; |
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(); |
@@ -467,21 +464,16 @@ class _Future<T> implements Future<T> { |
} |
_FutureListener listener = listeners; |
// 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. |
+ bool listenerHasError = hasError; |
+ final sourceResult = source._resultOrListeners; |
+ 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. |
@@ -497,14 +489,15 @@ class _Future<T> implements Future<T> { |
oldZone = Zone._enter(zone); |
} |
- bool handleValueCallback() { |
+ void handleValueCallback() { |
+ assert(!hasError); |
try { |
listenerValueOrError = zone.runUnary(listener._onValue, |
- sourceValue); |
- return true; |
+ sourceResult); |
+ listenerHasError = false; |
} catch (e, s) { |
listenerValueOrError = new AsyncError(e, s); |
- return false; |
+ listenerHasError = true; |
} |
} |
@@ -516,9 +509,10 @@ class _Future<T> implements Future<T> { |
try { |
matchesTest = zone.runUnary(test, asyncError.error); |
} catch (e, s) { |
- listenerValueOrError = identical(asyncError.error, e) ? |
- asyncError : new AsyncError(e, s); |
- listenerHasValue = false; |
+ listenerValueOrError = identical(asyncError.error, e) |
+ ? asyncError |
+ : new AsyncError(e, s); |
+ listenerHasError = true; |
return; |
} |
} |
@@ -533,21 +527,18 @@ class _Future<T> implements Future<T> { |
listenerValueOrError = zone.runUnary(errorCallback, |
asyncError.error); |
} |
+ listenerHasError = false; |
} catch (e, s) { |
- listenerValueOrError = identical(asyncError.error, e) ? |
- asyncError : new AsyncError(e, s); |
- listenerHasValue = false; |
- return; |
+ listenerValueOrError = identical(asyncError.error, e) |
+ ? asyncError |
+ : new AsyncError(e, s); |
+ listenerHasError = true; |
} |
- listenerHasValue = true; |
- } else { |
- // Copy over the error from the source. |
- listenerValueOrError = asyncError; |
- listenerHasValue = false; |
} |
} |
void handleWhenCompleteCallback() { |
+ assert(!listener.handlesError); |
var completeResult; |
try { |
completeResult = zone.run(listener._whenCompleteAction); |
@@ -557,49 +548,50 @@ class _Future<T> implements Future<T> { |
} else { |
listenerValueOrError = new AsyncError(e, s); |
} |
- listenerHasValue = false; |
+ listenerHasError = true; |
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); |
+ if (completeResult is _Future && completeResult._isComplete) { |
+ if (completeResult._hasError) { |
+ listenerValueOrError = completeResult._error; |
+ listenerHasError = true; |
} |
- _propagateToListeners(completeResult, |
- new _FutureListener.chain(result)); |
- }); |
+ // Otherwise use the existing result of source. |
+ return; |
+ } |
+ // 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; |
} |
} |
- if (!hasError) { |
- if (listener.handlesValue) { |
- listenerHasValue = handleValueCallback(); |
- } |
- } else { |
- handleError(); |
- } |
if (listener.handlesComplete) { |
+ // 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); |
handleWhenCompleteCallback(); |
+ } else if (!hasError) { |
+ if (listener.handlesValue) { |
+ handleValueCallback(); |
+ } |
+ } else { |
+ 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. |
@@ -607,7 +599,7 @@ class _Future<T> implements Future<T> { |
if (chainSource is _Future) { |
if (chainSource._isComplete) { |
// propagate the value (simulating a tail call). |
- result._isChained = true; |
+ result._setChained(); |
source = chainSource; |
listeners = new _FutureListener.chain(result); |
continue; |
@@ -622,7 +614,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; |