Mercurial > pub > ImplabNet
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) {