Beispiel #1
0
        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);
        }
Beispiel #2
0
        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);
            });
        }
Beispiel #3
0
        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);
                });
            }
        }
Beispiel #4
0
        internal static _Future immediate(FutureOr result)
        {
            var future = new _Future(Zone.current);

            future._asyncComplete(result);
            return(future);
        }
Beispiel #5
0
 public override void complete(FutureOr value = default)
 {
     if (!_future._mayComplete)
     {
         throw new Exception("Future already completed");
     }
     _future._complete(value);
 }
Beispiel #6
0
        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));
            }
        }
Beispiel #7
0
        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>());
        }
Beispiel #8
0
        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;
            }));
        }
Beispiel #9
0
        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>());
        }
Beispiel #10
0
 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);
     }
 }
Beispiel #11
0
 public abstract void complete(FutureOr value = default);
Beispiel #12
0
        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);
        }
Beispiel #13
0
 public static Future value(FutureOr value = default)
 {
     return(_Future.immediate(value));
 }
Beispiel #14
0
        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;
            }
        }