235 lines
7.8 KiB
C#
235 lines
7.8 KiB
C#
// /**
|
|
// * File: Promise.cs
|
|
// * Author: haraldwolff
|
|
// *
|
|
// * This file and it's content is copyrighted by the Author and / or copyright holder.
|
|
// * Any use wihtout proper permission is illegal and may lead to legal actions.
|
|
// *
|
|
// *
|
|
// **/
|
|
using System;
|
|
using System.Threading;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
namespace ln.type
|
|
{
|
|
public delegate void PromiseEvaluator<T>(Action<T> resolve, Action<object> reject);
|
|
public delegate void PromiseEvaluator<I,O>(I value, Action<O> resolve, Action<object> reject);
|
|
public delegate void PromiseResolved<T>(T value);
|
|
public delegate void PromiseRejected(object error);
|
|
|
|
public enum PromiseState { PENDING, RESOLVED, REJECTED }
|
|
|
|
public class Promise<T>
|
|
{
|
|
public event PromiseResolved<T> OnResolve;
|
|
public event PromiseRejected OnReject;
|
|
|
|
public PromiseState State { get; private set; } = PromiseState.PENDING;
|
|
|
|
PromiseEvaluator<T> Evaluator;
|
|
|
|
T resolvedValue;
|
|
object reason;
|
|
|
|
public T Value
|
|
{
|
|
get
|
|
{
|
|
if (State != PromiseState.RESOLVED)
|
|
throw new InvalidOperationException("promise not (yet) resolved");
|
|
return resolvedValue;
|
|
}
|
|
}
|
|
public object Reason
|
|
{
|
|
get
|
|
{
|
|
if (State != PromiseState.REJECTED)
|
|
throw new InvalidOperationException("promise not (yet) rejected");
|
|
return reason;
|
|
}
|
|
}
|
|
|
|
private Promise() { }
|
|
|
|
public Promise(PromiseEvaluator<T> evaluator) : this(evaluator, true) { }
|
|
private Promise(PromiseEvaluator<T> evaluator, bool queueJob)
|
|
{
|
|
Evaluator = evaluator;
|
|
if (queueJob)
|
|
ThreadPool.QueueUserWorkItem((state) => evaluator(resolve,reject ));
|
|
}
|
|
public Promise(Promise<T> parent, Action fin)
|
|
{
|
|
OnResolve += (value) => fin();
|
|
OnReject += (error) => fin();
|
|
parent.OnResolve += (value) => this.Resolve(value);
|
|
parent.OnReject += (error) => this.Reject(error);
|
|
}
|
|
|
|
protected Promise(PromiseResolved<T> onResolve, PromiseRejected onReject)
|
|
{
|
|
OnResolve += onResolve;
|
|
OnReject += onReject;
|
|
}
|
|
protected Promise(PromiseRejected onReject)
|
|
{
|
|
OnReject += onReject;
|
|
}
|
|
|
|
public Promise<T> Then(PromiseResolved<T> onResolve) => Then(onResolve, (e) => { });
|
|
public Promise<T> Then(PromiseResolved<T> onResolve, PromiseRejected onRejected) => new ChainedPromise<T>(this, (v, res, rej) => { res(v); onResolve(v); }, onRejected);
|
|
public Promise<T> Finally(Action action) => new Promise<T>(this, action);
|
|
|
|
public Promise<S> Then<S>(PromiseEvaluator<T, S> evaluator) => new ChainedPromise<S>(this, evaluator, (e)=>{} );
|
|
public Promise<S> Then<S>(PromiseEvaluator<T, S> evaluator, PromiseRejected rejected) => new ChainedPromise<S>(this, evaluator, rejected);
|
|
|
|
|
|
private void resolve(T value)
|
|
{
|
|
bool resolved;
|
|
lock (this)
|
|
{
|
|
resolved = (State == PromiseState.PENDING);
|
|
State = PromiseState.RESOLVED;
|
|
resolvedValue = value;
|
|
}
|
|
if (resolved)
|
|
OnResolve?.Invoke(value);
|
|
}
|
|
private void reject(object reason)
|
|
{
|
|
bool rejected;
|
|
lock (this)
|
|
{
|
|
rejected = (State == PromiseState.PENDING);
|
|
State = PromiseState.REJECTED;
|
|
this.reason = reason;
|
|
}
|
|
if (rejected)
|
|
OnReject?.Invoke(reason);
|
|
}
|
|
|
|
public Promise<T> Resolve(T value)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (State != PromiseState.PENDING)
|
|
throw new InvalidOperationException("Promise already settled");
|
|
|
|
resolve(value);
|
|
}
|
|
return this;
|
|
}
|
|
public Promise<T> Reject(object error)
|
|
{
|
|
lock (this)
|
|
{
|
|
if (State != PromiseState.PENDING)
|
|
throw new InvalidOperationException("Promise already settled");
|
|
reject(error);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public static Promise<T[]> All(IEnumerable<Promise<T>> promises)
|
|
{
|
|
Promise<T>[] sources = promises.ToArray();
|
|
T[] results = new T[sources.Length];
|
|
int cresolved = 0;
|
|
|
|
Promise<T[]> promise = new Promise<T[]>();
|
|
for (int n = 0; n < sources.Length; n++)
|
|
{
|
|
Promise<T> p = sources[n];
|
|
int nn = n;
|
|
|
|
lock (p)
|
|
{
|
|
switch (p.State)
|
|
{
|
|
case PromiseState.REJECTED:
|
|
return promise.Reject(p.Reason);
|
|
case PromiseState.RESOLVED:
|
|
cresolved++;
|
|
results[nn] = p.Value;
|
|
break;
|
|
case PromiseState.PENDING:
|
|
p.OnResolve += (value) => {
|
|
cresolved++;
|
|
results[nn] = p.Value;
|
|
|
|
if (cresolved == results.Length)
|
|
promise.resolve(results);
|
|
};
|
|
p.OnReject += (error) => {
|
|
promise.Reject(error);
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (cresolved == results.Length)
|
|
promise.resolve(results);
|
|
return promise;
|
|
}
|
|
|
|
public static Promise<T> Race(IEnumerable<Promise<T>> promises)
|
|
{
|
|
Promise<T>[] sources = promises.ToArray();
|
|
Promise<T> promise = new Promise<T>();
|
|
lock (promise)
|
|
{
|
|
for (int n = 0; n < sources.Length; n++)
|
|
{
|
|
Promise<T> p = sources[n];
|
|
int nn = n;
|
|
lock (p)
|
|
{
|
|
switch (p.State)
|
|
{
|
|
case PromiseState.REJECTED:
|
|
return promise.Reject(p.Reason);
|
|
case PromiseState.RESOLVED:
|
|
return promise.Resolve(p.Value);
|
|
case PromiseState.PENDING:
|
|
p.OnResolve += (value) => promise.resolve(value);
|
|
p.OnReject += (error) => promise.reject(error);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
class ChainedPromise<O> : Promise<O>
|
|
{
|
|
public ChainedPromise(Promise<T> parent, PromiseEvaluator<T,O> evalOnResolve, PromiseRejected onReject)
|
|
:base(onReject)
|
|
{
|
|
parent.OnResolve += (value) => {
|
|
evalOnResolve(
|
|
value,
|
|
(obj) => this.Resolve(obj),
|
|
(error) => this.Reject(error)
|
|
);
|
|
};
|
|
parent.OnReject += (error) => this.Reject(error);
|
|
}
|
|
}
|
|
|
|
public static implicit operator Promise<T>(T value) => new Promise<T>().Resolve(value);
|
|
}
|
|
|
|
public class Promise : Promise<Object>
|
|
{
|
|
public Promise(PromiseEvaluator<object> evaluator)
|
|
: base(evaluator)
|
|
{
|
|
}
|
|
}
|
|
}
|