843 lines
23 KiB
C#
843 lines
23 KiB
C#
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 C# promise.
|
|
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
///
|
|
/// This can also be waited on in a Unity coroutine and queried for its value.
|
|
/// </summary>
|
|
public interface IPromise<PromisedT>
|
|
{
|
|
/// <summary>
|
|
/// Set the name of the promise, useful for debugging.
|
|
/// </summary>
|
|
IPromise<PromisedT> WithName(string name);
|
|
|
|
/// <summary>
|
|
/// Completes the promise.
|
|
/// onResolved is called on successful completion.
|
|
/// onRejected is called on error.
|
|
/// </summary>
|
|
void Done(Action<PromisedT> onResolved, Action<Exception> onRejected);
|
|
|
|
/// <summary>
|
|
/// Completes the promise.
|
|
/// onResolved is called on successful completion.
|
|
/// Adds a default error handler.
|
|
/// </summary>
|
|
void Done(Action<PromisedT> onResolved);
|
|
|
|
/// <summary>
|
|
/// Complete the promise. Adds a default error handler.
|
|
/// </summary>
|
|
void Done();
|
|
|
|
/// <summary>
|
|
/// Handle errors for the promise.
|
|
/// </summary>
|
|
IPromise<PromisedT> 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<PromisedT, IPromise<ConvertedT>> onResolved);
|
|
|
|
/// <summary>
|
|
/// Add a resolved callback that chains a non-value promise.
|
|
/// </summary>
|
|
IPromise Then(Func<PromisedT, IPromise> onResolved);
|
|
|
|
/// <summary>
|
|
/// Add a resolved callback.
|
|
/// </summary>
|
|
IPromise<PromisedT> Then(Action<PromisedT> 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<PromisedT, 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<PromisedT, IPromise> onResolved, Action<Exception> onRejected);
|
|
|
|
/// <summary>
|
|
/// Add a resolved callback and a rejected callback.
|
|
/// </summary>
|
|
IPromise<PromisedT> Then(Action<PromisedT> onResolved, Action<Exception> onRejected);
|
|
|
|
/// <summary>
|
|
/// Return a new promise with a different value.
|
|
/// May also change the type of the value.
|
|
/// </summary>
|
|
IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, ConvertedT> transform);
|
|
|
|
/// <summary>
|
|
/// Return a new promise with a different value.
|
|
/// May also change the type of the value.
|
|
/// </summary>
|
|
[Obsolete("Use Then instead")]
|
|
IPromise<ConvertedT> Transform<ConvertedT>(Func<PromisedT, ConvertedT> transform);
|
|
|
|
/// <summary>
|
|
/// Chain an enumerable of promises, all of which must resolve.
|
|
/// Returns a promise for a collection of the resolved results.
|
|
/// 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<PromisedT, IEnumerable<IPromise<ConvertedT>>> 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 ThenAll(Func<PromisedT, IEnumerable<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.
|
|
/// Yields the value from the first promise that has resolved.
|
|
/// </summary>
|
|
IPromise<ConvertedT> ThenRace<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain);
|
|
|
|
/// <summary>
|
|
/// Takes a function that yields an enumerable of promises.
|
|
/// Converts to a non-value promise.
|
|
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
/// Yields the value from the first promise that has resolved.
|
|
/// </summary>
|
|
IPromise ThenRace(Func<PromisedT, IEnumerable<IPromise>> chain);
|
|
|
|
/// <summary>
|
|
/// Returns the resulting value if resolved.
|
|
/// Throws the rejection if rejected.
|
|
/// Throws an exception if not settled.
|
|
/// </summary>
|
|
PromisedT Value { get; }
|
|
|
|
/// <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()"
|
|
/// Once it finishes, use promise.Value to retrieve the value/error.
|
|
///
|
|
/// 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.
|
|
/// </summary>
|
|
public interface IRejectable
|
|
{
|
|
/// <summary>
|
|
/// Reject the promise with an exception.
|
|
/// </summary>
|
|
void Reject(Exception ex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interface for a promise that can be rejected or resolved.
|
|
/// </summary>
|
|
public interface IPendingPromise<PromisedT> : IRejectable
|
|
{
|
|
/// <summary>
|
|
/// Resolve the promise with a particular value.
|
|
/// </summary>
|
|
void Resolve(PromisedT value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the state of a promise.
|
|
/// </summary>
|
|
public enum PromiseState
|
|
{
|
|
Pending, // The promise is in-flight.
|
|
Rejected, // The promise has been rejected.
|
|
Resolved // The promise has been resolved.
|
|
};
|
|
|
|
/// <summary>
|
|
/// Implements a C# promise.
|
|
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
/// </summary>
|
|
public class Promise<PromisedT> : IPromise<PromisedT>, IPendingPromise<PromisedT>, IPromiseInfo
|
|
{
|
|
/// <summary>
|
|
/// The exception when the promise is rejected.
|
|
/// </summary>
|
|
private Exception rejectionException;
|
|
|
|
/// <summary>
|
|
/// The value when the promises is resolved.
|
|
/// </summary>
|
|
private PromisedT resolveValue;
|
|
|
|
/// <summary>
|
|
/// Error handler.
|
|
/// </summary>
|
|
private List<RejectHandler> rejectHandlers;
|
|
|
|
/// <summary>
|
|
/// Completed handlers that accept a value.
|
|
/// </summary>
|
|
private List<Action<PromisedT>> resolveCallbacks;
|
|
private List<IRejectable> resolveRejectables;
|
|
|
|
/// <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;
|
|
this.Id = ++Promise.nextPromiseId;
|
|
|
|
if (Promise.EnablePromiseTracking)
|
|
{
|
|
Promise.pendingPromises.Add(this);
|
|
}
|
|
}
|
|
|
|
public Promise(Action<Action<PromisedT>, Action<Exception>> resolver)
|
|
{
|
|
this.CurState = PromiseState.Pending;
|
|
this.Id = ++Promise.nextPromiseId;
|
|
|
|
if (Promise.EnablePromiseTracking)
|
|
{
|
|
Promise.pendingPromises.Add(this);
|
|
}
|
|
|
|
try
|
|
{
|
|
resolver(
|
|
// Resolve
|
|
value => Resolve(value),
|
|
|
|
// 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<PromisedT> onResolved, IRejectable rejectable)
|
|
{
|
|
if (resolveCallbacks == null)
|
|
{
|
|
resolveCallbacks = new List<Action<PromisedT>>();
|
|
}
|
|
|
|
if (resolveRejectables == null)
|
|
{
|
|
resolveRejectables = new List<IRejectable>();
|
|
}
|
|
|
|
resolveCallbacks.Add(onResolved);
|
|
resolveRejectables.Add(rejectable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke a single handler.
|
|
/// </summary>
|
|
private void InvokeHandler<T>(Action<T> callback, IRejectable rejectable, T value)
|
|
{
|
|
// Argument.NotNull(() => callback);
|
|
// Argument.NotNull(() => rejectable);
|
|
|
|
try
|
|
{
|
|
callback(value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
rejectable.Reject(ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function clear out all handlers after resolution or rejection.
|
|
/// </summary>
|
|
private void ClearHandlers()
|
|
{
|
|
rejectHandlers = null;
|
|
resolveCallbacks = null;
|
|
resolveRejectables = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke all reject handlers.
|
|
/// </summary>
|
|
private void InvokeRejectHandlers(Exception ex)
|
|
{
|
|
// Argument.NotNull(() => ex);
|
|
|
|
if (rejectHandlers != null)
|
|
{
|
|
rejectHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, ex));
|
|
}
|
|
|
|
ClearHandlers();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke all resolve handlers.
|
|
/// </summary>
|
|
private void InvokeResolveHandlers(PromisedT value)
|
|
{
|
|
if (resolveCallbacks != null)
|
|
{
|
|
for (int i = 0, maxI = resolveCallbacks.Count; i < maxI; i++) {
|
|
InvokeHandler(resolveCallbacks[i], resolveRejectables[i], value);
|
|
}
|
|
}
|
|
|
|
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 (Promise.EnablePromiseTracking)
|
|
{
|
|
Promise.pendingPromises.Remove(this);
|
|
}
|
|
|
|
InvokeRejectHandlers(ex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolve the promise with a particular value.
|
|
/// </summary>
|
|
public void Resolve(PromisedT value)
|
|
{
|
|
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);
|
|
}
|
|
|
|
resolveValue = value;
|
|
CurState = PromiseState.Resolved;
|
|
|
|
if (Promise.EnablePromiseTracking)
|
|
{
|
|
Promise.pendingPromises.Remove(this);
|
|
}
|
|
|
|
InvokeResolveHandlers(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Completes the promise.
|
|
/// onResolved is called on successful completion.
|
|
/// onRejected is called on error.
|
|
/// </summary>
|
|
public void Done(Action<PromisedT> 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<PromisedT> onResolved)
|
|
{
|
|
Then(onResolved)
|
|
.Catch(ex =>
|
|
Promise.PropagateUnhandledException(this, ex)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Complete the promise. Adds a default 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<PromisedT> WithName(string name)
|
|
{
|
|
this.Name = name;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle errors for the promise.
|
|
/// </summary>
|
|
public IPromise<PromisedT> Catch(Action<Exception> onRejected)
|
|
{
|
|
// Argument.NotNull(() => onRejected);
|
|
|
|
var resultPromise = new Promise<PromisedT>();
|
|
resultPromise.WithName(Name);
|
|
|
|
Action<PromisedT> resolveHandler = v =>
|
|
{
|
|
resultPromise.Resolve(v);
|
|
};
|
|
|
|
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<PromisedT, IPromise<ConvertedT>> onResolved)
|
|
{
|
|
return Then(onResolved, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a resolved callback that chains a non-value promise.
|
|
/// </summary>
|
|
public IPromise Then(Func<PromisedT, IPromise> onResolved)
|
|
{
|
|
return Then(onResolved, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a resolved callback.
|
|
/// </summary>
|
|
public IPromise<PromisedT> Then(Action<PromisedT> 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<PromisedT, 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<PromisedT> resolveHandler = v =>
|
|
{
|
|
onResolved(v)
|
|
.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<PromisedT, IPromise> onResolved, Action<Exception> onRejected)
|
|
{
|
|
var resultPromise = new Promise();
|
|
resultPromise.WithName(Name);
|
|
|
|
Action<PromisedT> resolveHandler = v =>
|
|
{
|
|
if (onResolved != null)
|
|
{
|
|
onResolved(v)
|
|
.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<PromisedT> Then(Action<PromisedT> onResolved, Action<Exception> onRejected)
|
|
{
|
|
var resultPromise = new Promise<PromisedT>();
|
|
resultPromise.WithName(Name);
|
|
|
|
Action<PromisedT> resolveHandler = v =>
|
|
{
|
|
if (onResolved != null)
|
|
{
|
|
onResolved(v);
|
|
}
|
|
|
|
resultPromise.Resolve(v);
|
|
};
|
|
|
|
Action<Exception> rejectHandler = ex =>
|
|
{
|
|
if (onRejected != null)
|
|
{
|
|
onRejected(ex);
|
|
}
|
|
|
|
resultPromise.Reject(ex);
|
|
};
|
|
|
|
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
|
|
return resultPromise;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a new promise with a different value.
|
|
/// May also change the type of the value.
|
|
/// </summary>
|
|
public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, ConvertedT> transform)
|
|
{
|
|
// Argument.NotNull(() => transform);
|
|
return Then(value => Promise<ConvertedT>.Resolved(transform(value)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a new promise with a different value.
|
|
/// May also change the type of the value.
|
|
/// </summary>
|
|
[Obsolete("Use Then instead")]
|
|
public IPromise<ConvertedT> Transform<ConvertedT>(Func<PromisedT, ConvertedT> transform)
|
|
{
|
|
// Argument.NotNull(() => transform);
|
|
return Then(value => Promise<ConvertedT>.Resolved(transform(value)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper function to invoke or register resolve/reject handlers.
|
|
/// </summary>
|
|
private void ActionHandlers(IRejectable resultPromise, Action<PromisedT> resolveHandler, Action<Exception> rejectHandler)
|
|
{
|
|
if (CurState == PromiseState.Resolved)
|
|
{
|
|
InvokeHandler(resolveHandler, resultPromise, resolveValue);
|
|
}
|
|
else if (CurState == PromiseState.Rejected)
|
|
{
|
|
InvokeHandler(rejectHandler, resultPromise, rejectionException);
|
|
}
|
|
else
|
|
{
|
|
AddResolveHandler(resolveHandler, resultPromise);
|
|
AddRejectHandler(rejectHandler, resultPromise);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Chain an enumerable of promises, all of which must resolve.
|
|
/// Returns a promise for a collection of the resolved results.
|
|
/// 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<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain)
|
|
{
|
|
return Then(value => Promise<ConvertedT>.All(chain(value)));
|
|
}
|
|
|
|
/// <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 ThenAll(Func<PromisedT, IEnumerable<IPromise>> chain)
|
|
{
|
|
return Then(value => Promise.All(chain(value)));
|
|
}
|
|
|
|
/// <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(params IPromise<PromisedT>[] promises)
|
|
{
|
|
return All((IEnumerable<IPromise<PromisedT>>)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<IEnumerable<PromisedT>> All(IEnumerable<IPromise<PromisedT>> promises)
|
|
{
|
|
var promisesArray = promises.ToArray();
|
|
if (promisesArray.Length == 0)
|
|
{
|
|
return Promise<IEnumerable<PromisedT>>.Resolved(EnumerableExt.Empty<PromisedT>());
|
|
}
|
|
|
|
var remainingCount = promisesArray.Length;
|
|
var results = new PromisedT[remainingCount];
|
|
var resultPromise = new Promise<IEnumerable<PromisedT>>();
|
|
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(result =>
|
|
{
|
|
results[index] = result;
|
|
|
|
--remainingCount;
|
|
if (remainingCount <= 0)
|
|
{
|
|
// This will never happen if any of the promises errorred.
|
|
resultPromise.Resolve(results);
|
|
}
|
|
})
|
|
.Done();
|
|
});
|
|
|
|
return resultPromise;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes a function that yields an enumerable of promises.
|
|
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
/// Yields the value from the first promise that has resolved.
|
|
/// </summary>
|
|
public IPromise<ConvertedT> ThenRace<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain)
|
|
{
|
|
return Then(value => Promise<ConvertedT>.Race(chain(value)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes a function that yields an enumerable of promises.
|
|
/// Converts to a non-value promise.
|
|
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
/// Yields the value from the first promise that has resolved.
|
|
/// </summary>
|
|
public IPromise ThenRace(Func<PromisedT, IEnumerable<IPromise>> chain)
|
|
{
|
|
return Then(value => Promise.Race(chain(value)));
|
|
}
|
|
|
|
public PromisedT Value
|
|
{
|
|
get
|
|
{
|
|
if (CurState == PromiseState.Pending) throw new InvalidOperationException("Promise not settled");
|
|
else if (CurState == PromiseState.Rejected) throw rejectionException;
|
|
return resolveValue;
|
|
}
|
|
}
|
|
|
|
class Enumerated<T> : IEnumerator {
|
|
private Promise<T> promise;
|
|
private bool abortOnFail;
|
|
|
|
public Enumerated(Promise<T> 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) {
|
|
var ret = new Enumerated<PromisedT>(this, abortOnFail);
|
|
//someone will poll for completion, so act like we've been terminated
|
|
Done(x => {}, ex => {});
|
|
return ret;
|
|
}
|
|
|
|
|
|
/// <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(params IPromise<PromisedT>[] promises)
|
|
{
|
|
return Race((IEnumerable<IPromise<PromisedT>>)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<PromisedT> Race(IEnumerable<IPromise<PromisedT>> 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<PromisedT>();
|
|
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(result =>
|
|
{
|
|
if (resultPromise.CurState == PromiseState.Pending)
|
|
{
|
|
resultPromise.Resolve(result);
|
|
}
|
|
})
|
|
.Done();
|
|
});
|
|
|
|
return resultPromise;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a simple value directly into a resolved promise.
|
|
/// </summary>
|
|
public static IPromise<PromisedT> Resolved(PromisedT promisedValue)
|
|
{
|
|
var promise = new Promise<PromisedT>();
|
|
promise.Resolve(promisedValue);
|
|
return promise;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert an exception directly into a rejected promise.
|
|
/// </summary>
|
|
public static IPromise<PromisedT> Rejected(Exception ex)
|
|
{
|
|
// Argument.NotNull(() => ex);
|
|
|
|
var promise = new Promise<PromisedT>();
|
|
promise.Reject(ex);
|
|
return promise;
|
|
}
|
|
|
|
}
|
|
}
|