|
|
using System;
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
namespace Implab {
|
|
|
|
|
|
/// <summary>
|
|
|
/// Класс для асинхронного получения результатов. Так называемое "обещание".
|
|
|
/// </summary>
|
|
|
/// <typeparam name="T">Тип получаемого результата</typeparam>
|
|
|
/// <remarks>
|
|
|
/// <para>Сервис при обращении к его методу дает обещаиние о выполнении операции,
|
|
|
/// клиент получив такое обещание может установить ряд обратных вызово для получения
|
|
|
/// событий выполнения обещания, тоесть завершения операции и предоставлении результатов.</para>
|
|
|
/// <para>
|
|
|
/// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на
|
|
|
/// данные события клиент должен использовать методы <c>Then</c>.
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой),
|
|
|
/// использует методы <c>Resolve</c> либо <c>Reject</c> для оповещения клиетна о
|
|
|
/// выполнении обещания.
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// Если сервер успел выполнить обещание еще до того, как клиент на него подписался,
|
|
|
/// то в момент подписки клиента будут вызваны соответсвующие события в синхронном
|
|
|
/// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в
|
|
|
/// список в порядке подписания и в этом же порядке они будут вызваны при выполнении
|
|
|
/// обещания.
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать
|
|
|
/// связанные асинхронные операции, которые также возвращают обещания. Для этого следует
|
|
|
/// использовать соответствующую форму методе <c>Then</c>.
|
|
|
/// </para>
|
|
|
/// <para>
|
|
|
/// Также хорошим правилом является то, что <c>Resolve</c> и <c>Reject</c> должен вызывать
|
|
|
/// только инициатор обещания иначе могут возникнуть противоречия.
|
|
|
/// </para>
|
|
|
/// </remarks>
|
|
|
public class Promise<T> : AbstractPromise<IDeferred<T>>, IPromise<T>, IDeferred<T> {
|
|
|
|
|
|
class StubDeferred : IDeferred, IDeferred<T> {
|
|
|
public static readonly StubDeferred instance = new StubDeferred();
|
|
|
|
|
|
StubDeferred() {
|
|
|
}
|
|
|
|
|
|
#region IDeferred implementation
|
|
|
|
|
|
public void Resolve(T value) {
|
|
|
}
|
|
|
|
|
|
public void Resolve() {
|
|
|
}
|
|
|
|
|
|
public void Reject(Exception error) {
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ICancellable implementation
|
|
|
|
|
|
public void Cancel() {
|
|
|
}
|
|
|
|
|
|
public void Cancel(Exception reason) {
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
class RemapDescriptor<T2> : IDeferred<T> {
|
|
|
readonly Func<T,T2> m_remap;
|
|
|
readonly Func<Exception, T2> m_failed;
|
|
|
readonly Func<Exception, T2> m_cancel;
|
|
|
readonly IDeferred<T2> m_deferred;
|
|
|
|
|
|
public RemapDescriptor(Func<T,T2> remap, Func<Exception,T2> failed, Func<Exception, T2> cancel, IDeferred<T2> deferred ) {
|
|
|
Debug.Assert(deferred != null);
|
|
|
m_remap = remap;
|
|
|
m_failed = failed;
|
|
|
m_cancel = cancel;
|
|
|
m_deferred = deferred;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region IDeferred implementation
|
|
|
|
|
|
public void Resolve(T value) {
|
|
|
if (m_remap != null) {
|
|
|
try {
|
|
|
m_deferred.Resolve(m_remap(value));
|
|
|
} catch (Exception ex) {
|
|
|
Reject(ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Reject(Exception error) {
|
|
|
if (m_failed != null) {
|
|
|
try {
|
|
|
m_deferred.Resolve(m_failed(error));
|
|
|
} catch (Exception ex) {
|
|
|
m_deferred.Reject(ex);
|
|
|
}
|
|
|
} else {
|
|
|
m_deferred.Reject(error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ICancellable implementation
|
|
|
|
|
|
public void Cancel(Exception reason) {
|
|
|
if (m_cancel != null) {
|
|
|
try {
|
|
|
m_deferred.Resolve(m_cancel(reason));
|
|
|
} catch (Exception ex) {
|
|
|
Reject(ex);
|
|
|
}
|
|
|
} else {
|
|
|
m_deferred.Cancel(reason);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Cancel() {
|
|
|
Cancel(null);
|
|
|
}
|
|
|
#endregion
|
|
|
}
|
|
|
|
|
|
class ListenerDescriptor : IDeferred<T> {
|
|
|
readonly Action m_handler;
|
|
|
readonly PromiseEventType m_events;
|
|
|
|
|
|
public ListenerDescriptor(Action handler, PromiseEventType events) {
|
|
|
Debug.Assert(handler != null);
|
|
|
|
|
|
m_handler = handler;
|
|
|
m_events = events;
|
|
|
}
|
|
|
|
|
|
#region IDeferred implementation
|
|
|
|
|
|
public void Resolve(T value) {
|
|
|
if (m_events.HasFlag(PromiseEventType.Success)) {
|
|
|
try {
|
|
|
m_handler();
|
|
|
// Analysis disable once EmptyGeneralCatchClause
|
|
|
} catch {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Reject(Exception error) {
|
|
|
if (m_events.HasFlag(PromiseEventType.Error)){
|
|
|
try {
|
|
|
m_handler();
|
|
|
// Analysis disable once EmptyGeneralCatchClause
|
|
|
} catch {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ICancellable implementation
|
|
|
|
|
|
public void Cancel() {
|
|
|
Cancel(null);
|
|
|
}
|
|
|
|
|
|
public void Cancel(Exception reason) {
|
|
|
if (m_events.HasFlag(PromiseEventType.Cancelled)){
|
|
|
try {
|
|
|
m_handler();
|
|
|
// Analysis disable once EmptyGeneralCatchClause
|
|
|
} catch {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
|
|
|
class ValueEventDescriptor : IDeferred<T> {
|
|
|
readonly Action<T> m_success;
|
|
|
readonly Action<Exception> m_failed;
|
|
|
readonly Action<Exception> m_cancelled;
|
|
|
readonly IDeferred<T> m_deferred;
|
|
|
|
|
|
public ValueEventDescriptor(Action<T> success, Action<Exception> failed, Action<Exception> cancelled, IDeferred<T> deferred) {
|
|
|
Debug.Assert(deferred != null);
|
|
|
|
|
|
m_success = success;
|
|
|
m_failed = failed;
|
|
|
m_cancelled = cancelled;
|
|
|
m_deferred = deferred;
|
|
|
}
|
|
|
|
|
|
#region IDeferred implementation
|
|
|
|
|
|
public void Resolve(T value) {
|
|
|
if (m_success != null) {
|
|
|
try {
|
|
|
m_success(value);
|
|
|
m_deferred.Resolve(value);
|
|
|
} catch (Exception ex) {
|
|
|
Reject(ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Reject(Exception error) {
|
|
|
if (m_failed != null) {
|
|
|
try {
|
|
|
m_failed(error);
|
|
|
m_deferred.Resolve(default(T));
|
|
|
} catch(Exception ex) {
|
|
|
m_deferred.Reject(ex);
|
|
|
}
|
|
|
} else {
|
|
|
m_deferred.Reject(error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ICancellable implementation
|
|
|
|
|
|
public void Cancel(Exception reason) {
|
|
|
if (m_cancelled != null) {
|
|
|
try {
|
|
|
m_cancelled(reason);
|
|
|
m_deferred.Resolve(default(T));
|
|
|
} catch (Exception ex) {
|
|
|
Reject(ex);
|
|
|
}
|
|
|
} else {
|
|
|
m_deferred.Cancel(reason);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Cancel() {
|
|
|
Cancel(null);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
|
|
|
public class EventDescriptor : IDeferred<T> {
|
|
|
readonly Action m_success;
|
|
|
readonly Action<Exception> m_failed;
|
|
|
readonly Action<Exception> m_cancelled;
|
|
|
readonly IDeferred m_deferred;
|
|
|
|
|
|
public EventDescriptor(Action success, Action<Exception> failed, Action<Exception> cancelled, IDeferred deferred) {
|
|
|
Debug.Assert(deferred != null);
|
|
|
|
|
|
m_success = success;
|
|
|
m_failed = failed;
|
|
|
m_cancelled = cancelled;
|
|
|
m_deferred = deferred;
|
|
|
}
|
|
|
|
|
|
#region IDeferred implementation
|
|
|
|
|
|
public void Resolve(T value) {
|
|
|
if (m_success != null) {
|
|
|
try {
|
|
|
m_success();
|
|
|
m_deferred.Resolve();
|
|
|
} catch (Exception ex) {
|
|
|
Reject(ex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Reject(Exception error) {
|
|
|
if (m_failed != null) {
|
|
|
try {
|
|
|
m_failed(error);
|
|
|
m_deferred.Resolve();
|
|
|
} catch (Exception ex) {
|
|
|
m_deferred.Reject(ex);
|
|
|
}
|
|
|
} else {
|
|
|
m_deferred.Reject(error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region ICancellable implementation
|
|
|
|
|
|
public void Cancel(Exception reason) {
|
|
|
if (m_cancelled != null) {
|
|
|
try {
|
|
|
m_cancelled(reason);
|
|
|
m_deferred.Resolve();
|
|
|
} catch (Exception ex) {
|
|
|
Reject(ex);
|
|
|
}
|
|
|
} else {
|
|
|
m_deferred.Cancel(reason);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Cancel() {
|
|
|
Cancel(null);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
|
|
|
T m_result;
|
|
|
|
|
|
public virtual void Resolve(T value) {
|
|
|
if (BeginSetResult()) {
|
|
|
m_result = value;
|
|
|
EndSetResult();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void Reject(Exception error) {
|
|
|
SetError(error);
|
|
|
}
|
|
|
|
|
|
public Type PromiseType {
|
|
|
get {
|
|
|
return typeof(T);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public new T Join() {
|
|
|
WaitResult(-1);
|
|
|
return m_result;
|
|
|
}
|
|
|
public new T Join(int timeout) {
|
|
|
WaitResult(timeout);
|
|
|
return m_result;
|
|
|
}
|
|
|
|
|
|
public IPromise<T> On(Action<T> success, Action<Exception> error, Action<Exception> cancel) {
|
|
|
AddHandler(new ValueEventDescriptor(success, error, cancel, StubDeferred.instance));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise<T> On(Action<T> success, Action<Exception> error) {
|
|
|
AddHandler(new ValueEventDescriptor(success, error, null, StubDeferred.instance));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise<T> On(Action<T> success) {
|
|
|
AddHandler(new ValueEventDescriptor(success, null, null, StubDeferred.instance));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise<T> On(Action handler, PromiseEventType events) {
|
|
|
Listen(events, handler);
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Then<T2>(Func<T, T2> mapper, Func<Exception, T2> error, Func<Exception, T2> cancel) {
|
|
|
var promise = new Promise<T2>();
|
|
|
if (mapper != null)
|
|
|
promise.On((Action)null, null, Cancel);
|
|
|
AddHandler(new RemapDescriptor<T2>(mapper, error, cancel, promise));
|
|
|
return promise;
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Then<T2>(Func<T, T2> mapper, Func<Exception, T2> error) {
|
|
|
var promise = new Promise<T2>();
|
|
|
if (mapper != null)
|
|
|
promise.On((Action)null, null, Cancel);
|
|
|
AddHandler(new RemapDescriptor<T2>(mapper, error, null, promise));
|
|
|
return promise;
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Then<T2>(Func<T, T2> mapper) {
|
|
|
var promise = new Promise<T2>();
|
|
|
if (mapper != null)
|
|
|
promise.On((Action)null, null, Cancel);
|
|
|
AddHandler(new RemapDescriptor<T2>(mapper, null, null, promise));
|
|
|
return promise;
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Chain<T2>(Func<T, IPromise<T2>> chained, Func<Exception, IPromise<T2>> error, Func<Exception, IPromise<T2>> cancel) {
|
|
|
// this promise will be resolved when an asyc operation is started
|
|
|
var promise = new Promise<IPromise<T2>>();
|
|
|
|
|
|
AddHandler(new RemapDescriptor<IPromise<T2>>(
|
|
|
chained,
|
|
|
error,
|
|
|
cancel,
|
|
|
promise
|
|
|
));
|
|
|
|
|
|
var medium = new Promise<T2>();
|
|
|
|
|
|
if (chained != null)
|
|
|
medium.On(Cancel, PromiseEventType.Cancelled);
|
|
|
|
|
|
// we need to connect started async operation with the medium
|
|
|
// if the async operation hasn't been started by the some reason
|
|
|
// report is to the medium
|
|
|
promise.On(
|
|
|
result => ConnectPromise<T2>(result, medium),
|
|
|
medium.Reject,
|
|
|
medium.Cancel
|
|
|
);
|
|
|
|
|
|
return medium;
|
|
|
}
|
|
|
|
|
|
static void ConnectPromise<T2>(IPromise<T2> result, Promise<T2> medium) {
|
|
|
if (result != null) {
|
|
|
result.On(
|
|
|
medium.Resolve,
|
|
|
medium.Reject,
|
|
|
medium.Cancel
|
|
|
);
|
|
|
medium.On(result.Cancel, PromiseEventType.Cancelled);
|
|
|
} else {
|
|
|
medium.Reject(
|
|
|
new NullReferenceException(
|
|
|
"The chained asynchronous operation returned" +
|
|
|
" 'null' where the promise instance is expected"
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Chain<T2>(Func<T, IPromise<T2>> chained, Func<Exception, IPromise<T2>> error) {
|
|
|
return Chain(chained, error, null);
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Chain<T2>(Func<T, IPromise<T2>> chained) {
|
|
|
return Chain(chained, null, null);
|
|
|
}
|
|
|
|
|
|
public IPromise Then(Action success, Action<Exception> error, Action<Exception> cancel) {
|
|
|
var promise = new Promise();
|
|
|
if (success != null)
|
|
|
promise.On(null, null, Cancel);
|
|
|
|
|
|
AddHandler(new EventDescriptor(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 Chain(Func<IPromise> chained, Func<Exception, IPromise> error, Func<Exception, IPromise> cancel) {
|
|
|
var promise = new Promise<IPromise>();
|
|
|
|
|
|
AddHandler(
|
|
|
new RemapDescriptor<IPromise>(
|
|
|
x => chained(),
|
|
|
error,
|
|
|
cancel,
|
|
|
promise
|
|
|
)
|
|
|
);
|
|
|
|
|
|
var medium = new Promise();
|
|
|
if (chained != null)
|
|
|
medium.On(null, null, Cancel);
|
|
|
|
|
|
promise.On(
|
|
|
result => ConnectPromise(result, medium),
|
|
|
medium.Reject,
|
|
|
medium.Cancel
|
|
|
);
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
public IPromise On(Action success, Action<Exception> error, Action<Exception> cancel) {
|
|
|
AddHandler(new EventDescriptor(success,error,cancel, StubDeferred.instance));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise On(Action success, Action<Exception> error) {
|
|
|
AddHandler(new EventDescriptor(success, error, null, StubDeferred.instance));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise On(Action success) {
|
|
|
Listen(PromiseEventType.Success, success);
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
IPromise IPromise.On(Action handler, PromiseEventType events) {
|
|
|
Listen(events,handler);
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
public IPromise<T2> Cast<T2>() {
|
|
|
return (IPromise<T2>)this;
|
|
|
}
|
|
|
|
|
|
#region implemented abstract members of AbstractPromise
|
|
|
|
|
|
protected override void SignalSuccess(IDeferred<T> handler) {
|
|
|
handler.Resolve(m_result);
|
|
|
}
|
|
|
|
|
|
protected override void SignalError(IDeferred<T> handler, Exception error) {
|
|
|
handler.Reject(error);
|
|
|
}
|
|
|
|
|
|
protected override void SignalCancelled(IDeferred<T> handler, Exception reason) {
|
|
|
handler.Cancel(reason);
|
|
|
}
|
|
|
|
|
|
protected override void Listen(PromiseEventType events, Action handler) {
|
|
|
if (handler != null)
|
|
|
AddHandler(new ListenerDescriptor(handler, events));
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
public static IPromise<T> ResultToPromise(T value) {
|
|
|
var p = new Promise<T>();
|
|
|
p.Resolve(value);
|
|
|
return p;
|
|
|
}
|
|
|
|
|
|
public static IPromise<T> ExceptionToPromise(Exception error) {
|
|
|
var p = new Promise<T>();
|
|
|
p.Reject(error);
|
|
|
return p;
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|