diff --git a/C-Sharp-Promise.csproj b/C-Sharp-Promise.csproj index 8b99fa3..8755b48 100644 --- a/C-Sharp-Promise.csproj +++ b/C-Sharp-Promise.csproj @@ -41,6 +41,7 @@ + diff --git a/Promise.cs b/Promise.cs index d96b9ff..8f5f3e0 100644 --- a/Promise.cs +++ b/Promise.cs @@ -10,12 +10,12 @@ namespace RSG /// Implements a C# promise. /// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise /// - public interface IPromise + public interface IPromise: IPromiseBase { /// /// Set the name of the promise, useful for debugging. /// - IPromise WithName(string name); + new IPromise WithName(string name); /// /// Completes the promise. @@ -31,15 +31,10 @@ public interface IPromise /// void Done(Action onResolved); - /// - /// Complete the promise. Adds a default error handler. - /// - void Done(); - /// /// Handle errors for the promise. /// - IPromise Catch(Action onRejected); + new IPromise Catch(Action onRejected); /// /// Add a resolved callback that chains a value promise (optionally converting to a different value type). @@ -116,17 +111,18 @@ public interface IPromise /// Yields the value from the first promise that has resolved. /// IPromise ThenRace(Func> chain); - } - /// - /// Interface for a promise that can be rejected. - /// - public interface IRejectable - { /// - /// Reject the promise with an exception. + /// Add a finally callback. + /// Finally callbacks will always be called, even if any preceding promise is rejected, or encounters an error. + /// + new IPromise Finally(Action onComplete); + + /// + /// Add a finally callback. + /// Finally callbacks will always be called, even if any preceding promise is rejected, or encounters an error. /// - void Reject(Exception ex); + IPromise Finally(Func> onComplete); } /// @@ -140,79 +136,28 @@ public interface IPendingPromise : IRejectable void Resolve(PromisedT value); } - /// - /// Specifies the state of a promise. - /// - public enum PromiseState - { - Pending, // The promise is in-flight. - Rejected, // The promise has been rejected. - Resolved // The promise has been resolved. - }; - /// /// Implements a C# promise. /// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise /// - public class Promise : IPromise, IPendingPromise, IPromiseInfo + public class Promise : Promise_Base, IPromise, IPendingPromise { - /// - /// The exception when the promise is rejected. - /// - private Exception rejectionException; - /// /// The value when the promises is resolved. /// private PromisedT resolveValue; - /// - /// Error handler. - /// - private List rejectHandlers; - /// /// Completed handlers that accept a value. /// private List> resolveCallbacks; private List resolveRejectables; - /// - /// ID of the promise, useful for debugging. - /// - public int Id { get; private set; } - - /// - /// Name of the promise, when set, useful for debugging. - /// - public string Name { get; private set; } - - /// - /// Tracks the current state of the promise. - /// - public PromiseState CurState { get; private set; } - - public Promise() - { - this.CurState = PromiseState.Pending; - this.Id = ++Promise.nextPromiseId; + public Promise() : base() + { } - if (Promise.EnablePromiseTracking) - { - Promise.pendingPromises.Add(this); - } - } - - public Promise(Action, Action> resolver) + public Promise(Action, Action> resolver) : this() { - this.CurState = PromiseState.Pending; - this.Id = ++Promise.nextPromiseId; - - if (Promise.EnablePromiseTracking) - { - Promise.pendingPromises.Add(this); - } - try { resolver( @@ -229,19 +174,6 @@ public Promise(Action, Action> resolver) } } - /// - /// Add a rejection handler for this promise. - /// - private void AddRejectHandler(Action onRejected, IRejectable rejectable) - { - if (rejectHandlers == null) - { - rejectHandlers = new List(); - } - - rejectHandlers.Add(new RejectHandler() { callback = onRejected, rejectable = rejectable }); ; - } - /// /// Add a resolve handler for this promise. /// @@ -261,49 +193,16 @@ private void AddResolveHandler(Action onResolved, IRejectable rejecta resolveRejectables.Add(rejectable); } - /// - /// Invoke a single handler. - /// - private void InvokeHandler(Action callback, IRejectable rejectable, T value) - { -// Argument.NotNull(() => callback); -// Argument.NotNull(() => rejectable); - - try - { - callback(value); - } - catch (Exception ex) - { - rejectable.Reject(ex); - } - } - /// /// Helper function clear out all handlers after resolution or rejection. /// - private void ClearHandlers() + protected override void ClearHandlers() { - rejectHandlers = null; + base.ClearHandlers(); resolveCallbacks = null; resolveRejectables = null; } - /// - /// Invoke all reject handlers. - /// - private void InvokeRejectHandlers(Exception ex) - { -// Argument.NotNull(() => ex); - - if (rejectHandlers != null) - { - rejectHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, ex)); - } - - ClearHandlers(); - } - /// /// Invoke all resolve handlers. /// @@ -319,29 +218,6 @@ private void InvokeResolveHandlers(PromisedT value) ClearHandlers(); } - /// - /// Reject the promise with an exception. - /// - 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); - } - /// /// Resolve the promise with a particular value. /// @@ -390,7 +266,33 @@ public void Done(Action onResolved) } /// - /// Complete the promise. Adds a default error handler. + /// Completes the promise. + /// onResolved is called on successful completion. + /// onRejected is called on error. + /// + void IPromiseBase.Done(Action onResolved, Action onRejected) + { + Then((x) => { onResolved(new PromiseResult(x)); }, onRejected) + .Catch(ex => + Promise.PropagateUnhandledException(this, ex) + ); + } + + /// + /// Completes the promise. + /// onResolved is called on successful completion. + /// Adds a default error handler. + /// + void IPromiseBase.Done(Action onResolved) + { + Then((x) => { onResolved(new PromiseResult(x)); }) + .Catch(ex => + Promise.PropagateUnhandledException(this, ex) + ); + } + + /// + /// Complete the promise. Adds a defualt error handler. /// public void Done() { @@ -408,6 +310,11 @@ public IPromise WithName(string name) return this; } + IPromiseBase IPromiseBase.WithName(string name) + { + return WithName(name); + } + /// /// Handle errors for the promise. /// @@ -435,6 +342,11 @@ public IPromise Catch(Action onRejected) return resultPromise; } + IPromiseBase IPromiseBase.Catch(Action onRejected) + { + return Catch(onRejected); + } + /// /// Add a resolved callback that chains a value promise (optionally converting to a different value type). /// @@ -451,6 +363,11 @@ public IPromise Then(Func onResolved) return Then(onResolved, null); } + IPromiseBase IPromiseBase.Then(Func onResolved) + { + return Then((x) => { onResolved(new PromiseResult(x)); }, null); + } + /// /// Add a resolved callback. /// @@ -459,6 +376,11 @@ public IPromise Then(Action onResolved) return Then(onResolved, null); } + IPromiseBase IPromiseBase.Then(Action onResolved) + { + return Then((x) => { onResolved(new PromiseResult(x)); }, null); + } + /// /// Add a resolved callback and a rejected callback. /// The resolved callback chains a value promise (optionally converting to a different value type). @@ -537,6 +459,11 @@ public IPromise Then(Func onResolved, Action onR return resultPromise; } + IPromiseBase IPromiseBase.Then(Func onResolved, Action onRejected) + { + return Then((x) => { onResolved(new PromiseResult(x)); }, onRejected); + } + /// /// Add a resolved callback and a rejected callback. /// @@ -570,6 +497,11 @@ public IPromise Then(Action onResolved, Action return resultPromise; } + IPromiseBase IPromiseBase.Then(Action onResolved, Action onRejected) + { + return Then((x) => { onResolved(new PromiseResult(x)); }, onRejected); + } + /// /// Return a new promise with a different value. /// May also change the type of the value. @@ -633,6 +565,11 @@ public IPromise ThenAll(Func> chain) return Then(value => Promise.All(chain(value))); } + IPromiseBase IPromiseBase.ThenAll(Func> chain) + { + return ThenAll((x) => { return chain(); }); + } + /// /// 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. @@ -777,5 +714,32 @@ public static IPromise Rejected(Exception ex) promise.Reject(ex); return promise; } + + public IPromise Finally(Action onComplete) + { + Promise promise = new Promise(); + promise.WithName(Name); + + this.Then((x) => { promise.Resolve(); }); + this.Catch((e) => { promise.Resolve(); }); + + return promise.Then(onComplete); + } + + public IPromise Finally(Func> onComplete) + { + Promise promise = new Promise(); + promise.WithName(Name); + + this.Then((x) => { promise.Resolve(); }); + this.Catch((e) => { promise.Resolve(); }); + + return promise.Then(onComplete); + } + + IPromiseBase IPromiseBase.Finally(Action onComplete) + { + return Finally(onComplete); + } } } \ No newline at end of file diff --git a/Promise_Base.cs b/Promise_Base.cs new file mode 100644 index 0000000..1f82478 --- /dev/null +++ b/Promise_Base.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Generic; +using RSG.Promises; + +namespace RSG +{ + public interface IPromiseBase + { + /// + /// Set the name of the promise, useful for debugging. + /// + IPromiseBase WithName(string name); + + /// + /// Completes the promise. + /// onResolved is called on successful completion. + /// onRejected is called on error. + /// + void Done(Action onResolved, Action onRejected); + + /// + /// Completes the promise. + /// onResolved is called on successful completion. + /// Adds a default error handler. + /// + void Done(Action onResolved); + + /// + /// Complete the promise. Adds a default error handler. + /// + void Done(); + + /// + /// Handle errors for the promise. + /// + IPromiseBase Catch(Action onRejected); + + /// + /// Add a resolved callback that chains a non-value promise. + /// + IPromiseBase Then(Func onResolved); + + /// + /// Add a resolved callback. + /// + IPromiseBase Then(Action onResolved); + + /// + /// Add a resolved callback and a rejected callback. + /// The resolved callback chains a non-value promise. + /// + IPromiseBase Then(Func onResolved, Action onRejected); + + /// + /// Add a resolved callback and a rejected callback. + /// + IPromiseBase Then(Action onResolved, Action onRejected); + + /// + /// 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. + /// + IPromiseBase ThenAll(Func> chain); + + /// + /// Add a finally callback. + /// Finally callbacks will always be called, even if any preceding promise is rejected, or encounters an error. + /// + IPromiseBase Finally(Action onComplete); + } + + /// + /// Interface for a promise that can be rejected. + /// + public interface IRejectable + { + /// + /// Reject the promise with an exception. + /// + void Reject(Exception ex); + } + + /// + /// Arguments to the UnhandledError event. + /// + public class ExceptionEventArgs : EventArgs + { + internal ExceptionEventArgs(Exception exception) + { + // Argument.NotNull(() => exception); + + this.Exception = exception; + } + + public Exception Exception + { + get; + private set; + } + } + + /// + /// Represents a handler invoked when the promise is rejected. + /// + public struct RejectHandler + { + /// + /// Callback fn. + /// + public Action callback; + + /// + /// The promise that is rejected when there is an error while invoking the handler. + /// + public IRejectable rejectable; + } + + public class PromiseResult + { + public static readonly PromiseResult None = new PromiseResult(); + + public readonly bool hasValue; + public readonly object Result; + + public PromiseResult() + { + hasValue = false; + } + + public PromiseResult(object value) + { + hasValue = true; + Result = value; + } + } + + /// + /// Specifies the state of a promise. + /// + public enum PromiseState + { + Pending, // The promise is in-flight. + Rejected, // The promise has been rejected. + Resolved // The promise has been resolved. + }; + + /// + /// Used to list information of pending promises. + /// + public interface IPromiseInfo + { + /// + /// Id of the promise. + /// + int Id { get; } + + /// + /// Human-readable name for the promise. + /// + string Name { get; } + } + + public abstract class Promise_Base : IPromiseInfo + { + /// + /// The exception when the promise is rejected. + /// + protected Exception rejectionException; + + /// + /// Error handlers. + /// + protected List rejectHandlers; + + /// + /// ID of the promise, useful for debugging. + /// + public int Id { get; protected set; } + + /// + /// Name of the promise, when set, useful for debugging. + /// + public string Name { get; protected set; } + + /// + /// Tracks the current state of the promise. + /// + public PromiseState CurState { get; protected set; } + + public Promise_Base() + { + this.CurState = PromiseState.Pending; + this.Id = ++Promise.nextPromiseId; + + if (Promise.EnablePromiseTracking) + { + Promise.pendingPromises.Add(this); + } + } + + /// + /// Add a rejection handler for this promise. + /// + protected void AddRejectHandler(Action onRejected, IRejectable rejectable) + { + if (rejectHandlers == null) + { + rejectHandlers = new List(); + } + + rejectHandlers.Add(new RejectHandler() + { + callback = onRejected, + rejectable = rejectable + }); + } + + /// + /// Invoke a single handler. + /// + protected void InvokeHandler(Action callback, IRejectable rejectable, T value) + { + // Argument.NotNull(() => callback); + // Argument.NotNull(() => rejectable); + + try + { + callback(value); + } + catch (Exception ex) + { + rejectable.Reject(ex); + } + } + + protected virtual void ClearHandlers() + { + rejectHandlers = null; + } + + /// + /// Invoke all reject handlers. + /// + protected void InvokeRejectHandlers(Exception ex) + { + // Argument.NotNull(() => ex); + + if (rejectHandlers != null) + { + rejectHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, ex)); + } + + ClearHandlers(); + } + + /// + /// Reject the promise with an exception. + /// + 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); + } + } +} diff --git a/Promise_NonGeneric.cs b/Promise_NonGeneric.cs index cc9905a..f0264d1 100644 --- a/Promise_NonGeneric.cs +++ b/Promise_NonGeneric.cs @@ -10,12 +10,12 @@ namespace RSG /// 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 /// - public interface IPromise + public interface IPromise : IPromiseBase { /// /// Set the name of the promise, useful for debugging. /// - IPromise WithName(string name); + new IPromise WithName(string name); /// /// Completes the promise. @@ -31,15 +31,10 @@ public interface IPromise /// void Done(Action onResolved); - /// - /// Complete the promise. Adds a default error handler. - /// - void Done(); - /// /// Handle errors for the promise. /// - IPromise Catch(Action onRejected); + new IPromise Catch(Action onRejected); /// /// Add a resolved callback that chains a value promise (optionally converting to a different value type). @@ -78,7 +73,7 @@ public interface IPromise /// 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. /// - IPromise ThenAll(Func> chain); + new IPromise ThenAll(Func> chain); /// /// Chain an enumerable of promises, all of which must resolve. @@ -108,75 +103,36 @@ public interface IPromise /// Returns a promise that resolves when the first of the promises has resolved. /// IPromise ThenRace(Func>> chain); - } - - /// - /// Interface for a promise that can be rejected or resolved. - /// - public interface IPendingPromise : IRejectable - { - /// - /// Resolve the promise with a particular value. - /// - void Resolve(); - } - /// - /// Used to list information of pending promises. - /// - public interface IPromiseInfo - { /// - /// Id of the promise. + /// Add a finally callback. + /// Finally callbacks will always be called, even if any preceding promise is rejected, or encounters an error. /// - int Id { get; } + new IPromise Finally(Action onComplete); /// - /// Human-readable name for the promise. + /// Add a finally callback. + /// Finally callbacks will always be called, even if any preceding promise is rejected, or encounters an error. /// - string Name { get; } + IPromise Finally(Func> onComplete); } /// - /// Arguments to the UnhandledError event. - /// - public class ExceptionEventArgs : EventArgs - { - internal ExceptionEventArgs(Exception exception) - { -// Argument.NotNull(() => exception); - - this.Exception = exception; - } - - public Exception Exception - { - get; - private set; - } - } - - /// - /// Represents a handler invoked when the promise is rejected. + /// Interface for a promise that can be rejected or resolved. /// - public struct RejectHandler + public interface IPendingPromise : IRejectable { /// - /// Callback fn. - /// - public Action callback; - - /// - /// The promise that is rejected when there is an error while invoking the handler. + /// Resolve the promise with a particular value. /// - public IRejectable rejectable; + void Resolve(); } /// /// 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 /// - public class Promise : IPromise, IPendingPromise, IPromiseInfo + public class Promise : Promise_Base, IPromise, IPendingPromise { /// /// Set to true to enable tracking of promises. @@ -213,16 +169,6 @@ public static IEnumerable GetPendingPromises() return pendingPromises; } - /// - /// The exception when the promise is rejected. - /// - private Exception rejectionException; - - /// - /// Error handlers. - /// - private List rejectHandlers; - /// /// Represents a handler invoked when the promise is resolved. /// @@ -244,38 +190,11 @@ public struct ResolveHandler /// private List resolveHandlers; - /// - /// ID of the promise, useful for debugging. - /// - public int Id { get; private set; } - - /// - /// Name of the promise, when set, useful for debugging. - /// - public string Name { get; private set; } - - /// - /// Tracks the current state of the promise. - /// - public PromiseState CurState { get; private set; } - - public Promise() - { - this.CurState = PromiseState.Pending; - if (EnablePromiseTracking) - { - pendingPromises.Add(this); - } - } + public Promise() : base() + { } - public Promise(Action> resolver) + public Promise(Action> resolver): this() { - this.CurState = PromiseState.Pending; - if (EnablePromiseTracking) - { - pendingPromises.Add(this); - } - try { resolver( @@ -292,23 +211,6 @@ public Promise(Action> resolver) } } - /// - /// Add a rejection handler for this promise. - /// - private void AddRejectHandler(Action onRejected, IRejectable rejectable) - { - if (rejectHandlers == null) - { - rejectHandlers = new List(); - } - - rejectHandlers.Add(new RejectHandler() - { - callback = onRejected, - rejectable = rejectable - }); - } - /// /// Add a resolve handler for this promise. /// @@ -365,27 +267,12 @@ private void InvokeResolveHandler(Action callback, IRejectable rejectable) /// /// Helper function clear out all handlers after resolution or rejection. /// - private void ClearHandlers() + override protected void ClearHandlers() { - rejectHandlers = null; + base.ClearHandlers(); resolveHandlers = null; } - /// - /// Invoke all reject handlers. - /// - private void InvokeRejectHandlers(Exception ex) - { -// Argument.NotNull(() => ex); - - if (rejectHandlers != null) - { - rejectHandlers.Each(handler => InvokeRejectHandler(handler.callback, handler.rejectable, ex)); - } - - ClearHandlers(); - } - /// /// Invoke all resolve handlers. /// @@ -399,30 +286,6 @@ private void InvokeResolveHandlers() ClearHandlers(); } - /// - /// Reject the promise with an exception. - /// - 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); - } - - /// /// Resolve the promise with a particular value. /// @@ -469,6 +332,32 @@ public void Done(Action onResolved) ); } + /// + /// Completes the promise. + /// onResolved is called on successful completion. + /// onRejected is called on error. + /// + void IPromiseBase.Done(Action onResolved, Action onRejected) + { + Then(() => { onResolved(PromiseResult.None); }, onRejected) + .Catch(ex => + Promise.PropagateUnhandledException(this, ex) + ); + } + + /// + /// Completes the promise. + /// onResolved is called on successful completion. + /// Adds a default error handler. + /// + void IPromiseBase.Done(Action onResolved) + { + Then(() => { onResolved(PromiseResult.None); }) + .Catch(ex => + Promise.PropagateUnhandledException(this, ex) + ); + } + /// /// Complete the promise. Adds a defualt error handler. /// @@ -488,6 +377,11 @@ public IPromise WithName(string name) return this; } + IPromiseBase IPromiseBase.WithName(string name) + { + return WithName(name); + } + /// /// Handle errors for the promise. /// @@ -515,6 +409,11 @@ public IPromise Catch(Action onRejected) return resultPromise; } + IPromiseBase IPromiseBase.Catch(Action onRejected) + { + return Catch(onRejected); + } + /// /// Add a resolved callback that chains a value promise (optionally converting to a different value type). /// @@ -531,6 +430,11 @@ public IPromise Then(Func onResolved) return Then(onResolved, null); } + IPromiseBase IPromiseBase.Then(Func onResolved) + { + return Then(() => { onResolved(PromiseResult.None); }); + } + /// /// Add a resolved callback. /// @@ -539,6 +443,11 @@ public IPromise Then(Action onResolved) return Then(onResolved, null); } + IPromiseBase IPromiseBase.Then(Action onResolved) + { + return Then(() => { onResolved(PromiseResult.None); }); + } + /// /// Add a resolved callback and a rejected callback. /// The resolved callback chains a value promise (optionally converting to a different value type). @@ -617,6 +526,11 @@ public IPromise Then(Func onResolved, Action onRejected) return resultPromise; } + IPromiseBase IPromiseBase.Then(Func onResolved, Action onRejected) + { + return Then(() => { onResolved(PromiseResult.None); }, onRejected); + } + /// /// Add a resolved callback and a rejected callback. /// @@ -650,6 +564,11 @@ public IPromise Then(Action onResolved, Action onRejected) return resultPromise; } + IPromiseBase IPromiseBase.Then(Action onResolved, Action onRejected) + { + return Then(() => { onResolved(PromiseResult.None); }, onRejected); + } + /// /// Helper function to invoke or register resolve/reject handlers. /// @@ -680,6 +599,11 @@ public IPromise ThenAll(Func> chain) return Then(() => Promise.All(chain())); } + IPromiseBase IPromiseBase.ThenAll(Func> chain) + { + return ThenAll(chain); + } + /// /// Chain an enumerable of promises, all of which must resolve. /// Converts to a non-value promise. @@ -876,5 +800,32 @@ internal static void PropagateUnhandledException(object sender, Exception ex) unhandlerException(sender, new ExceptionEventArgs(ex)); } } + + public IPromise Finally(Action onComplete) + { + Promise promise = new Promise(); + promise.WithName(Name); + + this.Then(() => { promise.Resolve(); }); + this.Catch((e) => { promise.Resolve(); }); + + return promise.Then(onComplete); + } + + public IPromise Finally(Func> onComplete) + { + Promise promise = new Promise(); + promise.WithName(Name); + + this.Then(() => { promise.Resolve(); }); + this.Catch((e) => { promise.Resolve(); }); + + return promise.Then(() => { return onComplete(); }); + } + + IPromiseBase IPromiseBase.Finally(Action onComplete) + { + return Finally(onComplete); + } } } \ No newline at end of file diff --git a/Tests/PromiseTests.cs b/Tests/PromiseTests.cs index 2847516..c867f71 100644 --- a/Tests/PromiseTests.cs +++ b/Tests/PromiseTests.cs @@ -986,5 +986,100 @@ public void exception_during_Then_onResolved_triggers_error_hander() Assert.Equal(0, callback); Assert.Equal(1, errorCallback); } + + [Fact] + public void finally_is_called_after_resolve() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }).Then((x) => { }); + + promise.Resolve(0); + + Assert.Equal(1, callback); + } + + [Fact] + public void finally_is_called_after_reject() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }); + + promise.Reject(new Exception()); + + Assert.Equal(1, callback); + } + + [Fact] + public void resolved_chain_continues_after_finally() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }) + .Then(() => + { + ++callback; + }); + + promise.Resolve(0); + + Assert.Equal(2, callback); + } + + [Fact] + public void rejected_chain_continues_after_finally() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }) + .Then(() => + { + ++callback; + }); + + promise.Reject(new Exception()); + + Assert.Equal(2, callback); + } + + [Fact] + public void can_chain_promise_after_finally() + { + var promise = new Promise(); + var expectedValue = 5; + var callback = 0; + + promise.Finally(() => + { + ++callback; + return Promise.Resolved(expectedValue); + }) + .Then((x) => + { + ++callback; + Assert.Equal(expectedValue, x); + }); + + promise.Resolve(0); + + Assert.Equal(2, callback); + } } } diff --git a/Tests/Promise_NonGeneric_Tests.cs b/Tests/Promise_NonGeneric_Tests.cs index 600fa55..46309bb 100644 --- a/Tests/Promise_NonGeneric_Tests.cs +++ b/Tests/Promise_NonGeneric_Tests.cs @@ -1110,5 +1110,100 @@ public void inner_exception_handled_by_outer_promise_with_results() Promise.UnhandledException -= handler; } } + + [Fact] + public void finally_is_called_after_resolve() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }); + + promise.Resolve(); + + Assert.Equal(1, callback); + } + + [Fact] + public void finally_is_called_after_reject() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }); + + promise.Reject(new Exception()); + + Assert.Equal(1, callback); + } + + [Fact] + public void resolved_chain_continues_after_finally() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }) + .Then(() => + { + ++callback; + }); + + promise.Resolve(); + + Assert.Equal(2, callback); + } + + [Fact] + public void rejected_chain_continues_after_finally() + { + var promise = new Promise(); + var callback = 0; + + promise.Finally(() => + { + ++callback; + }) + .Then(() => + { + ++callback; + }); + + promise.Reject(new Exception()); + + Assert.Equal(2, callback); + } + + [Fact] + public void can_chain_promise_after_finally() + { + var promise = new Promise(); + var expectedValue = 5; + var callback = 0; + + promise.Finally(() => + { + ++callback; + return Promise.Resolved(expectedValue); + }) + .Then((x) => + { + ++callback; + Assert.Equal(expectedValue, x); + }); + + promise.Resolve(); + + Assert.Equal(2, callback); + } } }