public static Future any(IEnumerable <Future> futures) { var completer = Completer.sync(); Func <object, FutureOr> onValue = (object value) => { if (!completer.isCompleted) { completer.complete(FutureOr.value(value)); } return(FutureOr.nil); }; Func <Exception, FutureOr> onError = (Exception error) => { if (!completer.isCompleted) { completer.completeError(error); } return(FutureOr.nil); }; foreach (var future in futures) { future.then(onValue, onError: onError); } return(completer.future); }
internal void _asyncComplete(FutureOr value) { D.assert(!_isComplete); // Two corner cases if the value is a future: // 1. the future is already completed and an error. // 2. the future is not yet completed but might become an error. // The first case means that we must not immediately complete the Future, // as our code would immediately start propagating the error without // giving the time to install error-handlers. // However the second case requires us to deal with the value immediately. // Otherwise the value could complete with an error and report an // unhandled error, even though we know we are already going to listen to // it. if (value.isFuture) { _chainFuture(value.f); return; } _setPendingComplete(); _zone.scheduleMicrotask(() => { _completeWithValue(value.v); return(null); }); }
static void _chainForeignFuture(Future source, _Future target) { D.assert(!target._isComplete); D.assert(!(source is _Future)); // Mark the target as chained (and as such half-completed). target._setPendingComplete(); try { source.then((value) => { D.assert(target._isPendingComplete); // The "value" may be another future if the foreign future // implementation is mis-behaving, // so use _complete instead of _completeWithValue. target._clearPendingComplete(); // Clear this first, it's set again. target._complete(FutureOr.value(value)); return(new FutureOr()); }, onError: (Exception error) => { D.assert(target._isPendingComplete); target._completeError(error); return(new FutureOr()); }); } catch (Exception e) { // 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. async_.scheduleMicrotask(() => { target._completeError(e); return(null); }); } }
internal static _Future immediate(FutureOr result) { var future = new _Future(Zone.current); future._asyncComplete(result); return(future); }
public override void complete(FutureOr value = default) { if (!_future._mayComplete) { throw new Exception("Future already completed"); } _future._complete(value); }
internal static void _cancelAndValue <T>(StreamSubscription <T> subscription, _Future future, object value) { var cancelFuture = subscription.cancel(); if (cancelFuture != null && !Equals(cancelFuture, Future._nullFuture)) { cancelFuture.whenComplete(() => future._complete(FutureOr.value(value))); } else { future._complete(FutureOr.value(value)); } }
Future <S> fold <S>(S initialValue, Func <S, T, S> combine) { _Future result = new _Future(); S value = initialValue; StreamSubscription <T> subscription = null; subscription = listen( (T element) => { _stream._runUserCode(() => combine(value, element), (S newValue) => { value = newValue; }, e => _stream._cancelAndErrorClosure(subscription, result)(e)); }, onError: (e, s) => result._completeError((Exception)e), onDone: () => { result._complete(FutureOr.value(value)); }, cancelOnError: true); return(result.to <S>()); }
public static Future forEach <T>(IEnumerable <T> elements, Func <T, FutureOr> action) { var iterator = elements.GetEnumerator(); return(doWhile(() => { if (!iterator.MoveNext()) { return false; } var result = action(iterator.Current); if (result.isFuture) { return FutureOr.future(result.f.then(_kTrue)); } return true; })); }
Future <T> reduce(Func <T, T, T> combine) { _Future result = new _Future(); bool seenFirst = false; T value = default; StreamSubscription <T> subscription = null; subscription = listen( (T element) => { if (seenFirst) { _stream._runUserCode(() => combine(value, element), (T newValue) => { value = newValue; }, onError: (e) => _stream._cancelAndErrorClosure(subscription, result)(e)); } else { value = element; seenFirst = true; } }, onError: (e, s) => result._completeError((Exception)e), onDone: () => { if (!seenFirst) { try { // Throw and recatch, instead of just doing // _completeWithErrorCallback, e, theError, StackTrace.current), // to ensure that the stackTrace is set on the error. throw new Exception("IterableElementError.noElement()"); } catch (Exception e) { async_._completeWithErrorCallback(result, e); } } else { // TODO: need check result._complete(FutureOr.value(value)); } }, cancelOnError: true); return(result.to <T>()); }
internal void _complete(FutureOr value = default) { D.assert(!_isComplete); if (value.isFuture) { if (value.f is _Future coreFuture) { _chainCoreFuture(coreFuture, this); } else { _chainForeignFuture(value.f, this); } } else { _FutureListener listeners = _removeListeners(); _setValue(value.v); _propagateToListeners(this, listeners); } }
public abstract void complete(FutureOr value = default);
public static Future wait <T>(IEnumerable <Future> futures, bool eagerError = false, Action <T> cleanUp = null) { _Future result = new _Future(); List <T> values = null; // Collects the values. Set to null on error. int remaining = 0; // How many futures are we waiting for. Exception error = null; // The first error from a future. Func <Exception, FutureOr> handleError = (Exception theError) => { remaining--; if (values != null) { if (cleanUp != null) { foreach (var value in values) { if (value != null) { // Ensure errors from cleanUp are uncaught. sync(() => { cleanUp(value); return(FutureOr.nil); }); } } } values = null; if (remaining == 0 || eagerError) { result._completeError(theError); } else { error = theError; } } else if (remaining == 0 && !eagerError) { result._completeError(error); } return(FutureOr.nil); }; try { // As each future completes, put its value into the corresponding // position in the list of values. foreach (var future in futures) { int pos = remaining; future.then((object value) => { remaining--; if (values != null) { values[pos] = (T)value; if (remaining == 0) { result._completeWithValue(values); } } else { if (cleanUp != null && value != null) { // Ensure errors from cleanUp are uncaught. sync(() => { cleanUp((T)value); return(FutureOr.nil); }); } if (remaining == 0 && !eagerError) { result._completeError(error); } } return(FutureOr.nil); }, onError: handleError); // Increment the 'remaining' after the call to 'then'. // If that call throws, we don't expect any future callback from // the future, and we also don't increment remaining. remaining++; } if (remaining == 0) { return(value(FutureOr.value(new List <T>()))); } values = new List <T>(new T[remaining]); } catch (Exception e) { // The error must have been thrown while iterating over the futures // list, or while installing a callback handler on the future. if (remaining == 0 || eagerError) { // Throw a new Future.error. // Don't just call `result._completeError` since that would propagate // the error too eagerly, not giving the callers time to install // error handlers. // Also, don't use `_asyncCompleteError` since that one doesn't give // zones the chance to intercept the error. return(Future.error(e)); } else { // Don't allocate a list for values, thus indicating that there was an // error. // Set error to the caught exception. error = e; } } return(result); }
public static Future value(FutureOr value = default) { return(_Future.immediate(value)); }
static void _propagateToListeners(_Future source, _FutureListener listeners) { while (true) { D.assert(source._isComplete); bool hasError = source._hasError; if (listeners == null) { if (hasError) { AsyncError asyncError = source._error; source._zone.handleUncaughtError(asyncError); } return; } // Usually futures only have one listener. If they have several, we // call handle them separately in recursive calls, continuing // here only when there is only one listener left. while (listeners._nextListener != null) { _FutureListener currentListener = listeners; listeners = currentListener._nextListener; currentListener._nextListener = null; _propagateToListeners(source, currentListener); } _FutureListener listener = listeners; var sourceResult = source._resultOrListeners; // Do the actual propagation. // 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 // 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. AsyncError asyncError = source._error; source._zone.handleUncaughtError(asyncError); return; } Zone oldZone = null; if (!ReferenceEquals(Zone.current, zone)) { // Change zone if it's not current. oldZone = Zone._enter(zone); } // These callbacks are abstracted to isolate the try/catch blocks // from the rest of the code to work around a V8 glass jaw. Action handleWhenCompleteCallback = () => { // The whenComplete-handler is not combined with normal value/error // handling. This means at most one handleX method is called per // listener. D.assert(!listener.handlesValue); D.assert(!listener.handlesError); FutureOr completeResult; try { completeResult = listener.handleWhenComplete(); } catch (Exception e) { if (hasError && ReferenceEquals(source._error.InnerException, e)) { listenerValueOrError = source._error; } else { listenerValueOrError = new AsyncError(e); } listenerHasError = true; return; } if (completeResult.isFuture) { var completeResultFuture = completeResult.f; if (completeResultFuture is _Future completeResultCoreFuture && completeResultCoreFuture._isComplete) { if (completeResultCoreFuture._hasError) { listenerValueOrError = completeResultCoreFuture._error; listenerHasError = true; } // 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 = completeResultFuture.then((_) => FutureOr.future(originalSource)); listenerHasError = false; } }; Action handleValueCallback = () => { try { listenerValueOrError = listener.handleValue(sourceResult); } catch (Exception e) { listenerValueOrError = new AsyncError(e); listenerHasError = true; } }; Action handleError = () => { try { AsyncError asyncError = source._error; if (listener.matchesErrorTest(asyncError) && listener.hasErrorCallback) { listenerValueOrError = listener.handleError(asyncError); listenerHasError = false; } } catch (Exception e) { if (ReferenceEquals(source._error.InnerException, e)) { listenerValueOrError = source._error; } else { listenerValueOrError = new AsyncError(e); } listenerHasError = true; } }; if (listener.handlesComplete) { 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 (listenerValueOrError is FutureOr futureOr) { listenerValueOrError = futureOr.isFuture ? futureOr.f : futureOr.v; } // If the listener's value is a future we need to chain it. Note that // this can only happen if there is a callback. if (listenerValueOrError is Future chainSource) { // Shortcut if the chain-source is already completed. Just continue // the loop. _Future listenerResult = listener.result; if (chainSource is _Future chainSourceCore) { if (chainSourceCore._isComplete) { listeners = listenerResult._removeListeners(); listenerResult._cloneResult(chainSourceCore); source = chainSourceCore; continue; } else { _chainCoreFuture(chainSourceCore, listenerResult); } } else { _chainForeignFuture(chainSource, listenerResult); } return; } } _Future result = listener.result; listeners = result._removeListeners(); if (!listenerHasError) { result._setValue(listenerValueOrError); } else { AsyncError asyncError = (AsyncError)listenerValueOrError; result._setErrorObject(asyncError); } // Prepare for next round. source = result; } }