/// <summary> /// Chain a sequence of operations using promises. /// Takes a collection of functions each of which starts an async operation and yields a promise. /// </summary> public static IPromise Sequence(IEnumerable <Func <IPromise> > fns) { var promise = new Promise(); int count = 0; fns.Aggregate( Resolved(), (prevPromise, fn) => { int itemSequence = count; ++count; return(prevPromise .Then(() => { var sliceLength = 1f / count; promise.ReportProgress(sliceLength * itemSequence); return fn(); }) .Progress(v => { var sliceLength = 1f / count; promise.ReportProgress(sliceLength * (v + itemSequence)); }) ); } ) .Then((Action)promise.Resolve) .Catch(promise.Reject); return(promise); }
/// <summary> /// Handle errors for the promise. /// </summary> public IPromise <PromisedT> Catch(Func <Exception, PromisedT> onRejected) { if (CurState == PromiseState.Resolved) { return(this); } var resultPromise = new Promise <PromisedT>(); resultPromise.WithName(Name); Action <PromisedT> resolveHandler = v => resultPromise.Resolve(v); Action <Exception> rejectHandler = ex => { try { resultPromise.Resolve(onRejected(ex)); } catch (Exception cbEx) { resultPromise.Reject(cbEx); } }; ActionHandlers(resultPromise, resolveHandler, rejectHandler); ProgressHandlers(resultPromise, v => resultPromise.ReportProgress(v)); return(resultPromise); }
/// <summary> /// Add a resolved callback, a rejected callback and a progress callback. /// The resolved callback chains a non-value promise. /// </summary> public IPromise Then(Func <IPromise> onResolved, Action <Exception> onRejected, Action <float> onProgress) { if (CurState == PromiseState.Resolved) { try { return(onResolved()); } catch (Exception ex) { return(Rejected(ex)); } } var resultPromise = new Promise(); resultPromise.WithName(Name); Action resolveHandler; if (onResolved != null) { resolveHandler = () => { onResolved() .Progress(progress => resultPromise.ReportProgress(progress)) .Then( () => resultPromise.Resolve(), ex => resultPromise.Reject(ex) ); }; } else { resolveHandler = resultPromise.Resolve; } Action <Exception> rejectHandler; if (onRejected != null) { rejectHandler = ex => { onRejected(ex); resultPromise.Reject(ex); }; } else { rejectHandler = resultPromise.Reject; } ActionHandlers(resultPromise, resolveHandler, rejectHandler); if (onProgress != null) { ProgressHandlers(this, onProgress); } return(resultPromise); }
/// <summary> /// Handle errors for the promise. /// </summary> public IPromise Catch(Action <Exception> onRejected) { // Argument.NotNull(() => onRejected); if (CurState == PromiseState.Resolved) { return(this); } var resultPromise = new Promise(); resultPromise.WithName(Name); Action resolveHandler = () => resultPromise.Resolve(); Action <Exception> rejectHandler = ex => { try { onRejected(ex); resultPromise.Resolve(); } catch (Exception callbackException) { resultPromise.Reject(callbackException); } }; ActionHandlers(resultPromise, resolveHandler, rejectHandler); ProgressHandlers(resultPromise, v => resultPromise.ReportProgress(v)); return(resultPromise); }
/// <summary> /// Chain a number of operations using promises. /// Returns the value of the first promise that resolves, or otherwise the exception thrown by the last operation. /// </summary> public static IPromise <T> First <T>(IEnumerable <Func <IPromise <T> > > fns) { var promise = new Promise <T>(); int count = 0; fns.Aggregate( Promise <T> .Rejected(null), (prevPromise, fn) => { int itemSequence = count; ++count; var newPromise = new Promise <T>(); prevPromise .Progress(v => { var sliceLength = 1f / count; promise.ReportProgress(sliceLength * (v + itemSequence)); }) .Then((Action <T>)newPromise.Resolve) .Catch(ex => { var sliceLength = 1f / count; promise.ReportProgress(sliceLength * itemSequence); fn() .Then(value => newPromise.Resolve(value)) .Catch(newPromise.Reject) .Done() ; }) ; return(newPromise); }) .Then(value => promise.Resolve(value)) .Catch(ex => { promise.ReportProgress(1f); promise.Reject(ex); }); return(promise); }
/// <summary> /// Returns a promise that resolves when all of the promises in the enumerable argument have resolved. /// Returns a promise of a collection of the resolved results. /// </summary> public static IPromise <IEnumerable <PromisedT> > All(IEnumerable <IPromise <PromisedT> > promises) { var promisesArray = promises.ToArray(); if (promisesArray.Length == 0) { return(Promise <IEnumerable <PromisedT> > .Resolved(Enumerable.Empty <PromisedT>())); } var remainingCount = promisesArray.Length; var results = new PromisedT[remainingCount]; var progress = new float[remainingCount]; var resultPromise = new Promise <IEnumerable <PromisedT> >(); resultPromise.WithName("All"); promisesArray.Each((promise, index) => { promise .Progress(v => { progress[index] = v; if (resultPromise.CurState == PromiseState.Pending) { resultPromise.ReportProgress(progress.Average()); } }) .Then(result => { progress[index] = 1f; results[index] = result; --remainingCount; if (remainingCount <= 0 && resultPromise.CurState == PromiseState.Pending) { // This will never happen if any of the promises errorred. resultPromise.Resolve(results); } }) .Catch(ex => { if (resultPromise.CurState == PromiseState.Pending) { // If a promise errorred and the result promise is still pending, reject it. resultPromise.Reject(ex); } }) .Done(); }); return(resultPromise); }
/// <summary> /// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved. /// Returns the value from the first promise that has resolved. /// </summary> public static IPromise <PromisedT> Race(IEnumerable <IPromise <PromisedT> > promises) { var promisesArray = promises.ToArray(); if (promisesArray.Length == 0) { throw new InvalidOperationException( "At least 1 input promise must be provided for Race" ); } var resultPromise = new Promise <PromisedT>(); resultPromise.WithName("Race"); var progress = new float[promisesArray.Length]; promisesArray.Each((promise, index) => { promise .Progress(v => { if (resultPromise.CurState == PromiseState.Pending) { progress[index] = v; resultPromise.ReportProgress(progress.Max()); } }) .Then(result => { if (resultPromise.CurState == PromiseState.Pending) { resultPromise.Resolve(result); } }) .Catch(ex => { if (resultPromise.CurState == PromiseState.Pending) { // If a promise errorred and the result promise is still pending, reject it. resultPromise.Reject(ex); } }) .Done(); }); return(resultPromise); }
/// <summary> /// Add a resolved callback, a rejected callback and a progress callback. /// The resolved callback chains a value promise (optionally converting to a different value type). /// </summary> public IPromise <ConvertedT> Then <ConvertedT>( Func <PromisedT, IPromise <ConvertedT> > onResolved, Func <Exception, IPromise <ConvertedT> > onRejected, Action <float> onProgress ) { if (CurState == PromiseState.Resolved) { try { return(onResolved(resolveValue)); } catch (Exception ex) { return(Promise <ConvertedT> .Rejected(ex)); } } // This version of the function must supply an onResolved. // Otherwise there is now way to get the converted value to pass to the resulting promise. // Argument.NotNull(() => onResolved); var resultPromise = new Promise <ConvertedT>(); resultPromise.WithName(Name); Action <PromisedT> resolveHandler = v => { onResolved(v) .Progress(progress => resultPromise.ReportProgress(progress)) .Then( // Should not be necessary to specify the arg type on the next line, but Unity (mono) has an internal compiler error otherwise. chainedValue => resultPromise.Resolve(chainedValue), ex => resultPromise.Reject(ex) ); }; Action <Exception> rejectHandler = ex => { if (onRejected == null) { resultPromise.Reject(ex); return; } try { onRejected(ex) .Then( chainedValue => resultPromise.Resolve(chainedValue), callbackEx => resultPromise.Reject(callbackEx) ); } catch (Exception callbackEx) { resultPromise.Reject(callbackEx); } }; ActionHandlers(resultPromise, resolveHandler, rejectHandler); if (onProgress != null) { ProgressHandlers(this, onProgress); } return(resultPromise); }