You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
930 lines
24 KiB
930 lines
24 KiB
using ZenFulcrum.EmbeddedBrowser.Promises; |
|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text; |
|
|
|
namespace ZenFulcrum.EmbeddedBrowser |
|
{ |
|
/// <summary> |
|
/// Implements a non-generic C# promise, this is a promise that simply resolves without delivering a value. |
|
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise |
|
/// |
|
/// This can also be waited on in a Unity coroutine. |
|
/// </summary> |
|
public interface IPromise |
|
{ |
|
/// <summary> |
|
/// Set the name of the promise, useful for debugging. |
|
/// </summary> |
|
IPromise WithName(string name); |
|
|
|
/// <summary> |
|
/// Completes the promise. |
|
/// onResolved is called on successful completion. |
|
/// onRejected is called on error. |
|
/// </summary> |
|
void Done(Action onResolved, Action<Exception> onRejected); |
|
|
|
/// <summary> |
|
/// Completes the promise. |
|
/// onResolved is called on successful completion. |
|
/// Adds a default error handler. |
|
/// </summary> |
|
void Done(Action onResolved); |
|
|
|
/// <summary> |
|
/// Complete the promise. Adds a default error handler. |
|
/// </summary> |
|
void Done(); |
|
|
|
/// <summary> |
|
/// Handle errors for the promise. |
|
/// </summary> |
|
IPromise Catch(Action<Exception> onRejected); |
|
|
|
/// <summary> |
|
/// Add a resolved callback that chains a value promise (optionally converting to a different value type). |
|
/// </summary> |
|
IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved); |
|
|
|
/// <summary> |
|
/// Add a resolved callback that chains a non-value promise. |
|
/// </summary> |
|
IPromise Then(Func<IPromise> onResolved); |
|
|
|
/// <summary> |
|
/// Add a resolved callback. |
|
/// </summary> |
|
IPromise Then(Action onResolved); |
|
|
|
/// <summary> |
|
/// Add a resolved callback and a rejected callback. |
|
/// The resolved callback chains a value promise (optionally converting to a different value type). |
|
/// </summary> |
|
IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved, Action<Exception> onRejected); |
|
|
|
/// <summary> |
|
/// Add a resolved callback and a rejected callback. |
|
/// The resolved callback chains a non-value promise. |
|
/// </summary> |
|
IPromise Then(Func<IPromise> onResolved, Action<Exception> onRejected); |
|
|
|
/// <summary> |
|
/// Add a resolved callback and a rejected callback. |
|
/// </summary> |
|
IPromise Then(Action onResolved, Action<Exception> onRejected); |
|
|
|
/// <summary> |
|
/// Chain an enumerable of promises, all of which must resolve. |
|
/// The resulting promise is resolved when all of the promises have resolved. |
|
/// It is rejected as soon as any of the promises have been rejected. |
|
/// </summary> |
|
IPromise ThenAll(Func<IEnumerable<IPromise>> chain); |
|
|
|
/// <summary> |
|
/// Chain an enumerable of promises, all of which must resolve. |
|
/// Converts to a non-value promise. |
|
/// The resulting promise is resolved when all of the promises have resolved. |
|
/// It is rejected as soon as any of the promises have been rejected. |
|
/// </summary> |
|
IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain); |
|
|
|
/// <summary> |
|
/// Chain a sequence of operations using promises. |
|
/// Reutrn a collection of functions each of which starts an async operation and yields a promise. |
|
/// Each function will be called and each promise resolved in turn. |
|
/// The resulting promise is resolved after each promise is resolved in sequence. |
|
/// </summary> |
|
IPromise ThenSequence(Func<IEnumerable<Func<IPromise>>> chain); |
|
|
|
/// <summary> |
|
/// Takes a function that yields an enumerable of promises. |
|
/// Returns a promise that resolves when the first of the promises has resolved. |
|
/// </summary> |
|
IPromise ThenRace(Func<IEnumerable<IPromise>> chain); |
|
|
|
/// <summary> |
|
/// Takes a function that yields an enumerable of promises. |
|
/// Converts to a value promise. |
|
/// Returns a promise that resolves when the first of the promises has resolved. |
|
/// </summary> |
|
IPromise<ConvertedT> ThenRace<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain); |
|
|
|
/// <summary> |
|
/// Returns an enumerable that yields null until the promise is settled. |
|
/// ("To WaitFor" like the WaitForXXYY functions Unity provides.) |
|
/// Suitable for use with a Unity coroutine's "yield return promise.ToWaitFor()" |
|
/// |
|
/// If throwOnFail is true, the coroutine will abort on promise rejection. |
|
/// </summary> |
|
/// <returns></returns> |
|
IEnumerator ToWaitFor(bool abortOnFail = false); |
|
} |
|
|
|
/// <summary> |
|
/// Interface for a promise that can be rejected or resolved. |
|
/// </summary> |
|
public interface IPendingPromise : IRejectable |
|
{ |
|
/// <summary> |
|
/// Resolve the promise with a particular value. |
|
/// </summary> |
|
void Resolve(); |
|
} |
|
|
|
/// <summary> |
|
/// Used to list information of pending promises. |
|
/// </summary> |
|
public interface IPromiseInfo |
|
{ |
|
/// <summary> |
|
/// Id of the promise. |
|
/// </summary> |
|
int Id { get; } |
|
|
|
/// <summary> |
|
/// Human-readable name for the promise. |
|
/// </summary> |
|
string Name { get; } |
|
} |
|
|
|
/// <summary> |
|
/// Arguments to the UnhandledError event. |
|
/// </summary> |
|
public class ExceptionEventArgs : EventArgs |
|
{ |
|
internal ExceptionEventArgs(Exception exception) |
|
{ |
|
// Argument.NotNull(() => exception); |
|
|
|
this.Exception = exception; |
|
} |
|
|
|
public Exception Exception |
|
{ |
|
get; |
|
private set; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Represents a handler invoked when the promise is rejected. |
|
/// </summary> |
|
public struct RejectHandler |
|
{ |
|
/// <summary> |
|
/// Callback fn. |
|
/// </summary> |
|
public Action<Exception> callback; |
|
|
|
/// <summary> |
|
/// The promise that is rejected when there is an error while invoking the handler. |
|
/// </summary> |
|
public IRejectable rejectable; |
|
} |
|
|
|
/// <summary> |
|
/// Implements a non-generic C# promise, this is a promise that simply resolves without delivering a value. |
|
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise |
|
/// </summary> |
|
public class Promise : IPromise, IPendingPromise, IPromiseInfo |
|
{ |
|
static Promise() |
|
{ |
|
UnhandledException += (sender, args) => { |
|
UnityEngine.Debug.LogWarning("Rejection: " + args.Exception.Message + "\n" + args.Exception.StackTrace); |
|
}; |
|
} |
|
|
|
/// <summary> |
|
/// Set to true to enable tracking of promises. |
|
/// </summary> |
|
public static bool EnablePromiseTracking = false; |
|
|
|
/// <summary> |
|
/// Event raised for unhandled errors. |
|
/// For this to work you have to complete your promises with a call to Done(). |
|
/// </summary> |
|
public static event EventHandler<ExceptionEventArgs> UnhandledException |
|
{ |
|
add { unhandlerException += value; } |
|
remove { unhandlerException -= value; } |
|
} |
|
private static EventHandler<ExceptionEventArgs> unhandlerException; |
|
|
|
/// <summary> |
|
/// Id for the next promise that is created. |
|
/// </summary> |
|
internal static int nextPromiseId = 0; |
|
|
|
/// <summary> |
|
/// Information about pending promises. |
|
/// </summary> |
|
internal static HashSet<IPromiseInfo> pendingPromises = new HashSet<IPromiseInfo>(); |
|
|
|
/// <summary> |
|
/// Information about pending promises, useful for debugging. |
|
/// This is only populated when 'EnablePromiseTracking' is set to true. |
|
/// </summary> |
|
public static IEnumerable<IPromiseInfo> GetPendingPromises() |
|
{ |
|
return pendingPromises; |
|
} |
|
|
|
/// <summary> |
|
/// The exception when the promise is rejected. |
|
/// </summary> |
|
private Exception rejectionException; |
|
|
|
/// <summary> |
|
/// Error handlers. |
|
/// </summary> |
|
private List<RejectHandler> rejectHandlers; |
|
|
|
/// <summary> |
|
/// Represents a handler invoked when the promise is resolved. |
|
/// </summary> |
|
public struct ResolveHandler |
|
{ |
|
/// <summary> |
|
/// Callback fn. |
|
/// </summary> |
|
public Action callback; |
|
|
|
/// <summary> |
|
/// The promise that is rejected when there is an error while invoking the handler. |
|
/// </summary> |
|
public IRejectable rejectable; |
|
} |
|
|
|
/// <summary> |
|
/// Completed handlers that accept no value. |
|
/// </summary> |
|
private List<ResolveHandler> resolveHandlers; |
|
|
|
/// <summary> |
|
/// ID of the promise, useful for debugging. |
|
/// </summary> |
|
public int Id { get; private set; } |
|
|
|
/// <summary> |
|
/// Name of the promise, when set, useful for debugging. |
|
/// </summary> |
|
public string Name { get; private set; } |
|
|
|
/// <summary> |
|
/// Tracks the current state of the promise. |
|
/// </summary> |
|
public PromiseState CurState { get; private set; } |
|
|
|
public Promise() |
|
{ |
|
this.CurState = PromiseState.Pending; |
|
if (EnablePromiseTracking) |
|
{ |
|
pendingPromises.Add(this); |
|
} |
|
} |
|
|
|
public Promise(Action<Action, Action<Exception>> resolver) |
|
{ |
|
this.CurState = PromiseState.Pending; |
|
if (EnablePromiseTracking) |
|
{ |
|
pendingPromises.Add(this); |
|
} |
|
|
|
try |
|
{ |
|
resolver( |
|
// Resolve |
|
() => Resolve(), |
|
|
|
// Reject |
|
ex => Reject(ex) |
|
); |
|
} |
|
catch (Exception ex) |
|
{ |
|
Reject(ex); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Add a rejection handler for this promise. |
|
/// </summary> |
|
private void AddRejectHandler(Action<Exception> onRejected, IRejectable rejectable) |
|
{ |
|
if (rejectHandlers == null) |
|
{ |
|
rejectHandlers = new List<RejectHandler>(); |
|
} |
|
|
|
rejectHandlers.Add(new RejectHandler() |
|
{ |
|
callback = onRejected, |
|
rejectable = rejectable |
|
}); |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolve handler for this promise. |
|
/// </summary> |
|
private void AddResolveHandler(Action onResolved, IRejectable rejectable) |
|
{ |
|
if (resolveHandlers == null) |
|
{ |
|
resolveHandlers = new List<ResolveHandler>(); |
|
} |
|
|
|
resolveHandlers.Add(new ResolveHandler() |
|
{ |
|
callback = onResolved, |
|
rejectable = rejectable |
|
}); |
|
} |
|
|
|
/// <summary> |
|
/// Invoke a single error handler. |
|
/// </summary> |
|
private void InvokeRejectHandler(Action<Exception> callback, IRejectable rejectable, Exception value) |
|
{ |
|
// Argument.NotNull(() => callback); |
|
// Argument.NotNull(() => rejectable); |
|
|
|
try |
|
{ |
|
callback(value); |
|
} |
|
catch (Exception ex) |
|
{ |
|
rejectable.Reject(ex); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Invoke a single resolve handler. |
|
/// </summary> |
|
private void InvokeResolveHandler(Action callback, IRejectable rejectable) |
|
{ |
|
// Argument.NotNull(() => callback); |
|
// Argument.NotNull(() => rejectable); |
|
|
|
try |
|
{ |
|
callback(); |
|
} |
|
catch (Exception ex) |
|
{ |
|
rejectable.Reject(ex); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Helper function clear out all handlers after resolution or rejection. |
|
/// </summary> |
|
private void ClearHandlers() |
|
{ |
|
rejectHandlers = null; |
|
resolveHandlers = null; |
|
} |
|
|
|
/// <summary> |
|
/// Invoke all reject handlers. |
|
/// </summary> |
|
private void InvokeRejectHandlers(Exception ex) |
|
{ |
|
// Argument.NotNull(() => ex); |
|
|
|
if (rejectHandlers != null) |
|
{ |
|
rejectHandlers.Each(handler => InvokeRejectHandler(handler.callback, handler.rejectable, ex)); |
|
} |
|
|
|
ClearHandlers(); |
|
} |
|
|
|
/// <summary> |
|
/// Invoke all resolve handlers. |
|
/// </summary> |
|
private void InvokeResolveHandlers() |
|
{ |
|
if (resolveHandlers != null) |
|
{ |
|
resolveHandlers.Each(handler => InvokeResolveHandler(handler.callback, handler.rejectable)); |
|
} |
|
|
|
ClearHandlers(); |
|
} |
|
|
|
/// <summary> |
|
/// Reject the promise with an exception. |
|
/// </summary> |
|
public void Reject(Exception ex) |
|
{ |
|
// Argument.NotNull(() => ex); |
|
|
|
if (CurState != PromiseState.Pending) |
|
{ |
|
throw new ApplicationException("Attempt to reject a promise that is already in state: " + CurState + ", a promise can only be rejected when it is still in state: " + PromiseState.Pending); |
|
} |
|
|
|
rejectionException = ex; |
|
CurState = PromiseState.Rejected; |
|
|
|
if (EnablePromiseTracking) |
|
{ |
|
pendingPromises.Remove(this); |
|
} |
|
|
|
InvokeRejectHandlers(ex); |
|
} |
|
|
|
|
|
/// <summary> |
|
/// Resolve the promise with a particular value. |
|
/// </summary> |
|
public void Resolve() |
|
{ |
|
if (CurState != PromiseState.Pending) |
|
{ |
|
throw new ApplicationException("Attempt to resolve a promise that is already in state: " + CurState + ", a promise can only be resolved when it is still in state: " + PromiseState.Pending); |
|
} |
|
|
|
CurState = PromiseState.Resolved; |
|
|
|
if (EnablePromiseTracking) |
|
{ |
|
pendingPromises.Remove(this); |
|
} |
|
|
|
InvokeResolveHandlers(); |
|
} |
|
|
|
/// <summary> |
|
/// Completes the promise. |
|
/// onResolved is called on successful completion. |
|
/// onRejected is called on error. |
|
/// </summary> |
|
public void Done(Action onResolved, Action<Exception> onRejected) |
|
{ |
|
Then(onResolved, onRejected) |
|
.Catch(ex => |
|
Promise.PropagateUnhandledException(this, ex) |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Completes the promise. |
|
/// onResolved is called on successful completion. |
|
/// Adds a default error handler. |
|
/// </summary> |
|
public void Done(Action onResolved) |
|
{ |
|
Then(onResolved) |
|
.Catch(ex => |
|
Promise.PropagateUnhandledException(this, ex) |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Complete the promise. Adds a defualt error handler. |
|
/// </summary> |
|
public void Done() |
|
{ |
|
Catch(ex => |
|
Promise.PropagateUnhandledException(this, ex) |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Set the name of the promise, useful for debugging. |
|
/// </summary> |
|
public IPromise WithName(string name) |
|
{ |
|
this.Name = name; |
|
return this; |
|
} |
|
|
|
/// <summary> |
|
/// Handle errors for the promise. |
|
/// </summary> |
|
public IPromise Catch(Action<Exception> onRejected) |
|
{ |
|
// Argument.NotNull(() => onRejected); |
|
|
|
var resultPromise = new Promise(); |
|
resultPromise.WithName(Name); |
|
|
|
Action resolveHandler = () => |
|
{ |
|
resultPromise.Resolve(); |
|
}; |
|
|
|
Action<Exception> rejectHandler = ex => |
|
{ |
|
onRejected(ex); |
|
|
|
resultPromise.Reject(ex); |
|
}; |
|
|
|
ActionHandlers(resultPromise, resolveHandler, rejectHandler); |
|
|
|
return resultPromise; |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolved callback that chains a value promise (optionally converting to a different value type). |
|
/// </summary> |
|
public IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved) |
|
{ |
|
return Then(onResolved, null); |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolved callback that chains a non-value promise. |
|
/// </summary> |
|
public IPromise Then(Func<IPromise> onResolved) |
|
{ |
|
return Then(onResolved, null); |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolved callback. |
|
/// </summary> |
|
public IPromise Then(Action onResolved) |
|
{ |
|
return Then(onResolved, null); |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolved callback and a rejected callback. |
|
/// The resolved callback chains a value promise (optionally converting to a different value type). |
|
/// </summary> |
|
public IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved, Action<Exception> onRejected) |
|
{ |
|
// 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 resolveHandler = () => |
|
{ |
|
onResolved() |
|
.Then( |
|
// Should not be necessary to specify the arg type on the next line, but Unity (mono) has an internal compiler error otherwise. |
|
(ConvertedT chainedValue) => resultPromise.Resolve(chainedValue), |
|
ex => resultPromise.Reject(ex) |
|
); |
|
}; |
|
|
|
Action<Exception> rejectHandler = ex => |
|
{ |
|
if (onRejected != null) |
|
{ |
|
onRejected(ex); |
|
} |
|
|
|
resultPromise.Reject(ex); |
|
}; |
|
|
|
ActionHandlers(resultPromise, resolveHandler, rejectHandler); |
|
|
|
return resultPromise; |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolved callback and a rejected callback. |
|
/// The resolved callback chains a non-value promise. |
|
/// </summary> |
|
public IPromise Then(Func<IPromise> onResolved, Action<Exception> onRejected) |
|
{ |
|
var resultPromise = new Promise(); |
|
resultPromise.WithName(Name); |
|
|
|
Action resolveHandler = () => |
|
{ |
|
if (onResolved != null) |
|
{ |
|
onResolved() |
|
.Then( |
|
() => resultPromise.Resolve(), |
|
ex => resultPromise.Reject(ex) |
|
); |
|
} |
|
else |
|
{ |
|
resultPromise.Resolve(); |
|
} |
|
}; |
|
|
|
Action<Exception> rejectHandler = ex => |
|
{ |
|
if (onRejected != null) |
|
{ |
|
onRejected(ex); |
|
} |
|
|
|
resultPromise.Reject(ex); |
|
}; |
|
|
|
ActionHandlers(resultPromise, resolveHandler, rejectHandler); |
|
|
|
return resultPromise; |
|
} |
|
|
|
/// <summary> |
|
/// Add a resolved callback and a rejected callback. |
|
/// </summary> |
|
public IPromise Then(Action onResolved, Action<Exception> onRejected) |
|
{ |
|
var resultPromise = new Promise(); |
|
resultPromise.WithName(Name); |
|
|
|
Action resolveHandler = () => |
|
{ |
|
if (onResolved != null) |
|
{ |
|
onResolved(); |
|
} |
|
|
|
resultPromise.Resolve(); |
|
}; |
|
|
|
Action<Exception> rejectHandler = ex => |
|
{ |
|
if (onRejected != null) |
|
{ |
|
onRejected(ex); |
|
} |
|
|
|
resultPromise.Reject(ex); |
|
}; |
|
|
|
ActionHandlers(resultPromise, resolveHandler, rejectHandler); |
|
|
|
return resultPromise; |
|
} |
|
|
|
/// <summary> |
|
/// Helper function to invoke or register resolve/reject handlers. |
|
/// </summary> |
|
private void ActionHandlers(IRejectable resultPromise, Action resolveHandler, Action<Exception> rejectHandler) |
|
{ |
|
if (CurState == PromiseState.Resolved) |
|
{ |
|
InvokeResolveHandler(resolveHandler, resultPromise); |
|
} |
|
else if (CurState == PromiseState.Rejected) |
|
{ |
|
InvokeRejectHandler(rejectHandler, resultPromise, rejectionException); |
|
} |
|
else |
|
{ |
|
AddResolveHandler(resolveHandler, resultPromise); |
|
AddRejectHandler(rejectHandler, resultPromise); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Chain an enumerable of promises, all of which must resolve. |
|
/// The resulting promise is resolved when all of the promises have resolved. |
|
/// It is rejected as soon as any of the promises have been rejected. |
|
/// </summary> |
|
public IPromise ThenAll(Func<IEnumerable<IPromise>> chain) |
|
{ |
|
return Then(() => Promise.All(chain())); |
|
} |
|
|
|
/// <summary> |
|
/// Chain an enumerable of promises, all of which must resolve. |
|
/// Converts to a non-value promise. |
|
/// The resulting promise is resolved when all of the promises have resolved. |
|
/// It is rejected as soon as any of the promises have been rejected. |
|
/// </summary> |
|
public IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain) |
|
{ |
|
return Then(() => Promise<ConvertedT>.All(chain())); |
|
} |
|
|
|
/// <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 All(params IPromise[] promises) |
|
{ |
|
return All((IEnumerable<IPromise>)promises); // Cast is required to force use of the other All function. |
|
} |
|
|
|
/// <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 All(IEnumerable<IPromise> promises) |
|
{ |
|
var promisesArray = promises.ToArray(); |
|
if (promisesArray.Length == 0) |
|
{ |
|
return Promise.Resolved(); |
|
} |
|
|
|
var remainingCount = promisesArray.Length; |
|
var resultPromise = new Promise(); |
|
resultPromise.WithName("All"); |
|
|
|
promisesArray.Each((promise, index) => |
|
{ |
|
promise |
|
.Catch(ex => |
|
{ |
|
if (resultPromise.CurState == PromiseState.Pending) |
|
{ |
|
// If a promise errorred and the result promise is still pending, reject it. |
|
resultPromise.Reject(ex); |
|
} |
|
}) |
|
.Then(() => |
|
{ |
|
--remainingCount; |
|
if (remainingCount <= 0) |
|
{ |
|
// This will never happen if any of the promises errorred. |
|
resultPromise.Resolve(); |
|
} |
|
}) |
|
.Done(); |
|
}); |
|
|
|
return resultPromise; |
|
} |
|
|
|
/// <summary> |
|
/// Chain a sequence of operations using promises. |
|
/// Reutrn a collection of functions each of which starts an async operation and yields a promise. |
|
/// Each function will be called and each promise resolved in turn. |
|
/// The resulting promise is resolved after each promise is resolved in sequence. |
|
/// </summary> |
|
public IPromise ThenSequence(Func<IEnumerable<Func<IPromise>>> chain) |
|
{ |
|
return Then(() => Sequence(chain())); |
|
} |
|
|
|
/// <summary> |
|
/// Chain a number of operations using promises. |
|
/// Takes a number of functions each of which starts an async operation and yields a promise. |
|
/// </summary> |
|
public static IPromise Sequence(params Func<IPromise>[] fns) |
|
{ |
|
return Sequence((IEnumerable<Func<IPromise>>)fns); |
|
} |
|
|
|
/// <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) |
|
{ |
|
return fns.Aggregate( |
|
Promise.Resolved(), |
|
(prevPromise, fn) => |
|
{ |
|
return prevPromise.Then(() => fn()); |
|
} |
|
); |
|
} |
|
|
|
/// <summary> |
|
/// Takes a function that yields an enumerable of promises. |
|
/// Returns a promise that resolves when the first of the promises has resolved. |
|
/// </summary> |
|
public IPromise ThenRace(Func<IEnumerable<IPromise>> chain) |
|
{ |
|
return Then(() => Promise.Race(chain())); |
|
} |
|
|
|
/// <summary> |
|
/// Takes a function that yields an enumerable of promises. |
|
/// Converts to a value promise. |
|
/// Returns a promise that resolves when the first of the promises has resolved. |
|
/// </summary> |
|
public IPromise<ConvertedT> ThenRace<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain) |
|
{ |
|
return Then(() => Promise<ConvertedT>.Race(chain())); |
|
} |
|
|
|
/// <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 Race(params IPromise[] promises) |
|
{ |
|
return Race((IEnumerable<IPromise>)promises); // Cast is required to force use of the other function. |
|
} |
|
|
|
/// <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 Race(IEnumerable<IPromise> promises) |
|
{ |
|
var promisesArray = promises.ToArray(); |
|
if (promisesArray.Length == 0) |
|
{ |
|
throw new ApplicationException("At least 1 input promise must be provided for Race"); |
|
} |
|
|
|
var resultPromise = new Promise(); |
|
resultPromise.WithName("Race"); |
|
|
|
promisesArray.Each((promise, index) => |
|
{ |
|
promise |
|
.Catch(ex => |
|
{ |
|
if (resultPromise.CurState == PromiseState.Pending) |
|
{ |
|
// If a promise errorred and the result promise is still pending, reject it. |
|
resultPromise.Reject(ex); |
|
} |
|
}) |
|
.Then(() => |
|
{ |
|
if (resultPromise.CurState == PromiseState.Pending) |
|
{ |
|
resultPromise.Resolve(); |
|
} |
|
}) |
|
.Done(); |
|
}); |
|
|
|
return resultPromise; |
|
} |
|
|
|
/// <summary> |
|
/// Convert a simple value directly into a resolved promise. |
|
/// </summary> |
|
public static IPromise Resolved() |
|
{ |
|
var promise = new Promise(); |
|
promise.Resolve(); |
|
return promise; |
|
} |
|
|
|
/// <summary> |
|
/// Convert an exception directly into a rejected promise. |
|
/// </summary> |
|
public static IPromise Rejected(Exception ex) |
|
{ |
|
// Argument.NotNull(() => ex); |
|
|
|
var promise = new Promise(); |
|
promise.Reject(ex); |
|
return promise; |
|
} |
|
|
|
/// <summary> |
|
/// Raises the UnhandledException event. |
|
/// </summary> |
|
internal static void PropagateUnhandledException(object sender, Exception ex) |
|
{ |
|
if (unhandlerException != null) |
|
{ |
|
unhandlerException(sender, new ExceptionEventArgs(ex)); |
|
} |
|
} |
|
|
|
class Enumerated : IEnumerator { |
|
private Promise promise; |
|
private bool abortOnFail; |
|
|
|
public Enumerated(Promise promise, bool abortOnFail) { |
|
this.promise = promise; |
|
this.abortOnFail = abortOnFail; |
|
} |
|
|
|
public bool MoveNext() { |
|
if (abortOnFail && promise.CurState == PromiseState.Rejected) { |
|
throw promise.rejectionException; |
|
} |
|
return promise.CurState == PromiseState.Pending; |
|
} |
|
|
|
public void Reset() { } |
|
|
|
public object Current { get { return null; } } |
|
} |
|
|
|
|
|
public IEnumerator ToWaitFor(bool abortOnFail = false) { |
|
var ret = new Enumerated(this, abortOnFail); |
|
//someone will poll for completion, so act like we've been terminated |
|
Done(() => {}, ex => {}); |
|
return ret; |
|
} |
|
|
|
} |
|
}
|
|
|