// /** // * 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(Action resolve, Action reject); public delegate void PromiseEvaluator(I value, Action resolve, Action reject); public delegate void PromiseResolved(T value); public delegate void PromiseRejected(object error); public enum PromiseState { PENDING, RESOLVED, REJECTED } public class Promise { public event PromiseResolved OnResolve; public event PromiseRejected OnReject; public PromiseState State { get; private set; } = PromiseState.PENDING; PromiseEvaluator 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 evaluator) : this(evaluator, true) { } private Promise(PromiseEvaluator evaluator, bool queueJob) { Evaluator = evaluator; if (queueJob) ThreadPool.QueueUserWorkItem((state) => evaluator(resolve,reject )); } public Promise(Promise parent, Action fin) { OnResolve += (value) => fin(); OnReject += (error) => fin(); parent.OnResolve += (value) => this.Resolve(value); parent.OnReject += (error) => this.Reject(error); } protected Promise(PromiseResolved onResolve, PromiseRejected onReject) { OnResolve += onResolve; OnReject += onReject; } protected Promise(PromiseRejected onReject) { OnReject += onReject; } public Promise Then(PromiseResolved onResolve) => Then(onResolve, (e) => { }); public Promise Then(PromiseResolved onResolve, PromiseRejected onRejected) => new ChainedPromise(this, (v, res, rej) => { res(v); onResolve(v); }, onRejected); public Promise Finally(Action action) => new Promise(this, action); public Promise Then(PromiseEvaluator evaluator) => new ChainedPromise(this, evaluator, (e)=>{} ); public Promise Then(PromiseEvaluator evaluator, PromiseRejected rejected) => new ChainedPromise(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 Resolve(T value) { lock (this) { if (State != PromiseState.PENDING) throw new InvalidOperationException("Promise already settled"); resolve(value); } return this; } public Promise Reject(object error) { lock (this) { if (State != PromiseState.PENDING) throw new InvalidOperationException("Promise already settled"); reject(error); } return this; } public static Promise All(IEnumerable> promises) { Promise[] sources = promises.ToArray(); T[] results = new T[sources.Length]; int cresolved = 0; Promise promise = new Promise(); for (int n = 0; n < sources.Length; n++) { Promise 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 Race(IEnumerable> promises) { Promise[] sources = promises.ToArray(); Promise promise = new Promise(); lock (promise) { for (int n = 0; n < sources.Length; n++) { Promise 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 : Promise { public ChainedPromise(Promise parent, PromiseEvaluator 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 value) => new Promise().Resolve(value); } public class Promise : Promise { public Promise(PromiseEvaluator evaluator) : base(evaluator) { } } }