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); }
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 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>()); }
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>()); }
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); }