diff Implab/Promise.cs @ 76:c761fc982e1d v2

Refactoring of the IPromise<T> interface Added tests
author cin
date Wed, 10 Sep 2014 17:53:05 +0400
parents 4439140706d0
children 4f20870d0816
line wrap: on
line diff
--- a/Implab/Promise.cs	Wed Sep 10 11:17:37 2014 +0400
+++ b/Implab/Promise.cs	Wed Sep 10 17:53:05 2014 +0400
@@ -11,7 +11,6 @@
     public delegate T ErrorHandler<out T>(Exception e);
     public delegate void ResultHandler<in T>(T result);
     public delegate TNew ResultMapper<in TSrc,out TNew>(TSrc result);
-    public delegate IPromise<TNew> ChainedOperation<in TSrc,TNew>(TSrc result);
 
     /// <summary>
     /// Класс для асинхронного получения результатов. Так называемое "обещание".
@@ -121,10 +120,15 @@
         public Promise(IPromise parent, bool cancellable) {
             m_cancellable = cancellable;
             if (parent != null)
-                Cancelled(() => {
-                    if (parent.IsExclusive)
-                        parent.Cancel();
-                });
+                AddHandler(
+                    null,
+                    null,
+                    () => {
+                        if (parent.IsExclusive)
+                            parent.Cancel();
+                    },
+                    null
+                );
         }
 
         bool BeginTransit() {
@@ -210,22 +214,14 @@
         /// <summary>
         /// Отменяет операцию, если это возможно.
         /// </summary>
-        /// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns>
-        public bool Cancel() {
+        /// <remarks>Для определения была ли операция отменена следует использовать свойство <see cref="IsCancelled"/>.</remarks>
+        public void Cancel() {
             if (m_cancellable && BeginTransit()) {
                 CompleteTransit(CANCELLED_STATE);
                 OnStateChanged();
-                return true;
             }
-            return false;
         }
 
-        // сделано для возвращаемого типа void
-        protected void InternalCancel() {
-            Cancel();
-        }
-
-
         public IPromise<T> Then(ResultHandler<T> success, ErrorHandler<T> error, Action cancel) {
             if (success == null && error == null && cancel == null)
                 return this;
@@ -255,30 +251,7 @@
             return medium;
         }
 
-        public IPromise Then(Action success, ErrorHandler error, Action cancel) {
-            return Then(
-                x => success(),
-                e => {
-                    error(e);
-                    return default(T);
-                },
-                cancel
-            );
-        }
-
-        public IPromise Then(Action success, ErrorHandler error) {
-            return Then(
-                x => success(),
-                e => {
-                    error(e);
-                    return default(T);
-                }
-            );
-        }
-
-        public IPromise Then(Action success) {
-            return Then(x => success());
-        }
+        
 
 
         public IPromise<T> Then(ResultHandler<T> success) {
@@ -292,6 +265,23 @@
             return medium;
         }
 
+        /// <summary>
+        /// Последний обработчик в цепочки обещаний.
+        /// </summary>
+        /// <param name="success"></param>
+        /// <param name="error"></param>
+        /// <param name="cancel"></param>
+        /// <remarks>
+        /// <para>
+        /// Данный метод не создает связанного с текущим обещания и предназначен для окончания
+        /// фсинхронной цепочки.
+        /// </para>
+        /// <para>
+        /// Если данный метод вызвать несколько раз, либо добавить другие обработчики, то цепочка
+        /// не будет одиночной <see cref="IsExclusive"/> и, как следствие, будет невозможна отмена
+        /// всей цепи обещаний снизу (с самого последнего обещания).
+        /// </para>
+        /// </remarks>
         public void Last(ResultHandler<T> success, ErrorHandler error, Action cancel) {
             if (success == null && error == null && cancel == null)
                 return;
@@ -313,18 +303,6 @@
             Last(success, null, null);
         }
 
-        public void Last(Action success,ErrorHandler error, Action cancel) {
-            Last(x => success(), error, cancel);
-        }
-
-        public void Last(Action success,ErrorHandler error) {
-            Last(x => success(), error, null);
-        }
-
-        public void Last(Action success) {
-            Last(x => success(), null, null);
-        }
-
         public IPromise Error(ErrorHandler error) {
             if (error == null)
                 return this;
@@ -371,44 +349,56 @@
         /// <param name="error">Обработчик ошибки. Данный обработчик получит
         /// исключение возникшее при выполнении операции.</param>
         /// <returns>Новое обещание, которое будет выполнено при выполнении исходного обещания.</returns>
-        public IPromise<TNew> Then<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler<T> error) {
-            if (mapper == null)
-                throw new ArgumentNullException("mapper");
+        public IPromise<TNew> Then<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler<TNew> error, Action cancel) {
+            Safe.ArgumentNotNull(mapper, "mapper");
+            
+            // создаем прицепленное обещание
+            var medium = new Promise<TNew>(this, true);
 
-            // создаем прицепленное обещание
-            var chained = new Promise<TNew>(this, true);
-
-            ResultHandler<T> resultHandler = result => chained.Resolve(mapper(result));
+            ResultHandler<T> resultHandler = result => medium.Resolve(mapper(result));
             ErrorHandler<T> errorHandler;
             if (error != null)
                 errorHandler = e => {
                     try {
-                        return error(e);
+                        medium.Resolve(error(e));
                     } catch (Exception e2) {
                         // в случае ошибки нужно передать исключение дальше по цепочке
-                        chained.Reject(e2);
+                        medium.Reject(e2);
                     }
                     return default(T);
                 };
             else
                 errorHandler = e => {
-                    chained.Reject(e);
+                    medium.Reject(e);
                     return default(T);
                 };
 
+            Action cancelHandler;
+            if (cancel != null)
+                cancelHandler = () => {
+                    cancel();
+                    medium.Cancel();
+                };
+            else
+                cancelHandler = medium.Cancel;
+
 
             AddHandler(
                 resultHandler,
                 errorHandler,
-                chained.InternalCancel,
+                cancelHandler,
                 null
             );
 
-            return chained;
+            return medium;
+        }
+
+        public IPromise<TNew> Then<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler<TNew> error) {
+            return Then(mapper, error, null);
         }
 
         public IPromise<TNew> Then<TNew>(ResultMapper<T, TNew> mapper) {
-            return Then(mapper, null);
+            return Then(mapper, null, null);
         }
 
         /// <summary>
@@ -421,7 +411,9 @@
         /// <param name="error">Обработчик ошибки. Данный обработчик получит
         /// исключение возникшее при выполнении текуещй операции.</param>
         /// <returns>Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции.</returns>
-        public IPromise<TNew> Then<TNew>(ChainedOperation<T, TNew> chained, ErrorHandler<T> error) {
+        public IPromise<TNew> Chain<TNew>(ResultMapper<T, IPromise<TNew>> chained, ErrorHandler<IPromise<TNew>> error, Action cancel) {
+
+            Safe.ArgumentNotNull(chained, "chained");
 
             // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно
             // создать посредника, к которому будут подвызяваться следующие обработчики.
@@ -435,12 +427,10 @@
 
                 var promise = chained(result);
 
-                promise.Then(
+                promise.Last(
                     medium.Resolve,
-                    err => {
-                        medium.Reject(err);
-                        throw new TransientPromiseException(err);
-                    }
+                    medium.Reject,
+                    () => medium.Reject(new OperationCanceledException()) // внешняя отмена связанной операции рассматривается как ошибка
                 );
                 
                 // notify chained operation that it's not needed anymore
@@ -450,41 +440,70 @@
                     if (promise.IsExclusive)
                         promise.Cancel();
                 });
-
-                // внешняя отмена связанной операции рассматривается как ошибка
-                promise.Cancelled(() => medium.Reject(new OperationCanceledException()));
             };
 
-            ErrorHandler<T> errorHandler = delegate(Exception e) {
-                if (error != null) {
+            ErrorHandler<T> errorHandler;
+
+            if (error != null)
+                errorHandler = delegate(Exception e) {
                     try {
-                        return error(e);
+                        var promise = error(e);
+
+                        promise.Last(
+                            medium.Resolve,
+                            medium.Reject,
+                            () => medium.Reject(new OperationCanceledException()) // внешняя отмена связанной операции рассматривается как ошибка
+                        );
+
+                        // notify chained operation that it's not needed anymore
+                        // порядок вызова Then, Cancelled важен, поскольку от этого
+                        // зависит IsExclusive
+                        medium.Cancelled(() => {
+                            if (promise.IsExclusive)
+                                promise.Cancel();
+                        });
                     } catch (Exception e2) {
                         medium.Reject(e2);
-                        return default(T);
                     }
-                }
-                // в случае ошибки нужно передать исключение дальше по цепочке
-                medium.Reject(e);
-                return default(T);
-            };
+                    return default(T);
+                };
+            else
+                errorHandler = err => {
+                    medium.Reject(err);
+                    return default(T);
+                };
+
+
+            Action cancelHandler;
+            if (cancel != null)
+                cancelHandler = () => {
+                    if (cancel != null)
+                        cancel();
+                    medium.Cancel();
+                };
+            else
+                cancelHandler = medium.Cancel;
 
             AddHandler(
                 resultHandler,
                 errorHandler,
-                medium.InternalCancel,
+                cancelHandler,
                 null
             );
 
             return medium;
         }
 
-        public IPromise<TNew> Then<TNew>(ChainedOperation<T, TNew> chained) {
-            return Then(chained, null);
+        public IPromise<TNew> Chain<TNew>(ResultMapper<T, IPromise<TNew>> chained, ErrorHandler<IPromise<TNew>> error) {
+            return Chain(chained, error, null);
+        }
+
+        public IPromise<TNew> Chain<TNew>(ResultMapper<T, IPromise<TNew>> chained) {
+            return Chain(chained, null, null);
         }
 
         public IPromise<T> Cancelled(Action handler) {
-            var medium = new Promise<T>(this, true);
+            var medium = new Promise<T>(this,true);
             AddHandler(null, null, handler, medium);
             return medium;
         }
@@ -494,9 +513,9 @@
         /// </summary>
         /// <param name="handler">The handler that will be called anyway</param>
         /// <returns>self</returns>
-        public IPromise<T> Finally(Action handler) {
-            if (handler == null)
-                throw new ArgumentNullException("handler");
+        public IPromise<T> Anyway(Action handler) {
+            Safe.ArgumentNotNull(handler, "handler");
+            
             AddHandler(
                 x => handler(),
                 e => {
@@ -541,7 +560,7 @@
         /// <returns>Результат выполнения обещания</returns>
         public T Join(int timeout) {
             var evt = new ManualResetEvent(false);
-            Finally(() => evt.Set());
+            Anyway(() => evt.Set());
 
             if (!evt.WaitOne(timeout, true))
                 throw new TimeoutException();
@@ -736,12 +755,49 @@
 
         #region IPromiseBase explicit implementation
 
+        IPromise IPromise.Then(Action success, ErrorHandler error, Action cancel) {
+            return Then(
+                x => success(),
+                e => {
+                    error(e);
+                    return default(T);
+                },
+                cancel
+            );
+        }
+
+        IPromise IPromise.Then(Action success, ErrorHandler error) {
+            return Then(
+                x => success(),
+                e => {
+                    error(e);
+                    return default(T);
+                }
+            );
+        }
+
+        IPromise IPromise.Then(Action success) {
+            return Then(x => success());
+        }
+
+        void IPromise.Last(Action success, ErrorHandler error, Action cancel) {
+            Last(x => success(), error, cancel);
+        }
+
+        void IPromise.Last(Action success, ErrorHandler error) {
+            Last(x => success(), error, null);
+        }
+
+        void IPromise.Last(Action success) {
+            Last(x => success(), null, null);
+        }
+
         IPromise IPromise.Error(ErrorHandler error) {
             return Error(error);
         }
 
-        IPromise IPromise.Finally(Action handler) {
-            return Finally(handler);
+        IPromise IPromise.Anyway(Action handler) {
+            return Anyway(handler);
         }
 
         IPromise IPromise.Cancelled(Action handler) {