view Implab/Promise.cs @ 138:f75cfa58e3d4 v2

added ICancellable.Cancel(Exception) to allow specify the reason of cancellation
author cin
date Tue, 17 Feb 2015 18:16:26 +0300
parents 2573b562e328
children 8c0b95069066
line wrap: on
line source

using System;
using System.Diagnostics;

namespace Implab {
    public class Promise : AbstractPromise<Promise.HandlerDescriptor>, IPromise, IDeferred {

        public struct HandlerDescriptor {
            readonly Action m_success;
            readonly Action<Exception> m_error;
            readonly Action<Exception> m_cancel;
            readonly IDeferred m_deferred;

            public HandlerDescriptor(Action success, Action<Exception> error, Action<Exception> cancel, IDeferred deferred) {
                m_success = success;
                m_error = error;
                m_cancel = cancel;
                m_deferred = deferred;
            }

            public void SignalSuccess() {
                if (m_success != null) {
                    try {
                        m_success();
                        if (m_deferred != null)
                            m_deferred.Resolve();
                    } catch (Exception err) {
                        SignalError(err);
                    }
                }
            }

            public void SignalError(Exception err) {
                if (m_error != null) {
                    try {
                        m_error(err);
                        if (m_deferred != null)
                            m_deferred.Resolve();
                    } catch (Exception err2) {
                        if (m_deferred != null)
                            m_deferred.Reject(err2);
                    }
                } else {
                    if (m_deferred != null)
                        m_deferred.Reject(err);
                }
            }

            public void SignalCancel(Exception reason) {
                if (m_cancel != null) {
                    try {
                        m_cancel(reason);
                        if (m_deferred != null)
                            m_deferred.Resolve();
                    } catch (Exception err) {
                        SignalError(err);
                    }
                } else if (reason != null && m_error != null) {
                    try {
                        m_error(new OperationCanceledException("The operation was canceled.", reason));
                        if (m_deferred != null)
                            m_deferred.Resolve();
                    } catch (Exception err) {
                        SignalError(err);
                    }
                } else {
                    if (m_deferred != null)
                        m_deferred.Cancel(reason);
                }
            }
        }

        public void Resolve() {
            BeginSetResult();
            EndSetResult();
        }

        public void Reject(Exception error) {
            SetError(error);
        }

        #region implemented abstract members of AbstractPromise

        protected override void SignalSuccess(HandlerDescriptor handler) {
            handler.SignalSuccess();
        }

        protected override void SignalError(HandlerDescriptor handler, Exception error) {
            handler.SignalError(error);
        }

        protected override void SignalCancelled(HandlerDescriptor handler, Exception reason) {
            handler.SignalCancel(reason);
        }

        protected override void Listen(PromiseEventType events, Action handler) {
            AddHandler(new HandlerDescriptor(
                events.HasFlag(PromiseEventType.Success) ? handler : null,
                events.HasFlag(PromiseEventType.Error) ? new Action<Exception>(err => handler()) : null,
                events.HasFlag(PromiseEventType.Cancelled) ? new Action<Exception>(reason => handler()) : null,
                null
            ));
        }

        #endregion


        public Type PromiseType {
            get {
                return typeof(void);
            }
        }

        public IPromise Then(Action success, Action<Exception> error, Action<Exception> cancel) {
            var promise = new Promise();
            if (success != null)
                promise.On(Cancel, PromiseEventType.Cancelled);

            AddHandler(new HandlerDescriptor(success, error, cancel, promise));

            return promise;
        }

        public IPromise Then(Action success, Action<Exception> error) {
            return Then(success, error, null);
        }

        public IPromise Then(Action success) {
            return Then(success, null, null);
        }

        public IPromise On(Action success, Action<Exception> error, Action<Exception> cancel) {
            AddHandler(new HandlerDescriptor(success, error, cancel, null));
            return this;
        }

        public IPromise On(Action success, Action<Exception> error) {
            return On(success, error, null);
        }

        public IPromise On(Action success) {
            return On(success, null, null);
        }

        public IPromise On(Action handler, PromiseEventType events) {
            return On(
                events.HasFlag(PromiseEventType.Success) ? handler : null,
                events.HasFlag(PromiseEventType.Error) ? new Action<Exception>(err => handler()) : null,
                events.HasFlag(PromiseEventType.Cancelled) ? new Action<Exception>(reason => handler()) : null
            );
        }

        public IPromise<T> Cast<T>() {
            throw new InvalidCastException();
        }

        public IPromise Chain(Func<IPromise> chained, Func<Exception, IPromise> error, Func<Exception,IPromise> cancel) {
            var medium = new Promise();

            On(
                () => {
                    if (medium.IsCancelled)
                        return;
                    if (chained != null)
                        ConnectPromise(chained(), medium);
                },
                ex => {
                    if (medium.IsCancelled)
                        return;
                    if (error != null) {
                        try {
                            ConnectPromise(error(ex), medium);
                        } catch (Exception ex2) {
                            medium.Reject(ex2);
                        }
                    } else {
                        medium.Reject(ex);
                    }
                },
                reason => {
                    if (medium.IsCancelled)
                        return;
                    if (cancel != null)
                        ConnectPromise(cancel(reason), medium);
                    else
                        medium.Cancel(reason);
                }
            );

            if (chained != null)
                medium.On(Cancel, PromiseEventType.Cancelled);

            return medium;
        }

        static void ConnectPromise(IPromise result, Promise medium) {
            if (result != null) {
                result.On(
                    medium.Resolve,
                    medium.Reject,
                    medium.Cancel
                );
                medium.On(null,null,result.Cancel);
            } else {
                medium.Reject(
                    new NullReferenceException(
                        "The chained asynchronous operation returned" +
                        " 'null' where the promise instance is expected"
                    )
                );
            }
        }

        public IPromise Chain(Func<IPromise> chained, Func<Exception, IPromise> error) {
            return Chain(chained, error, null);
        }

        public IPromise Chain(Func<IPromise> chained) {
            return Chain(chained, null, null);
        }
    }
}