Skip to content

Instantly share code, notes, and snippets.

@cuppster
Created September 3, 2012 18:39
Show Gist options
  • Star 51 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save cuppster/3612000 to your computer and use it in GitHub Desktop.
Save cuppster/3612000 to your computer and use it in GitHub Desktop.
Promises for C# using Generics
/*
modified from original source: https://bitbucket.org/mattkotsenas/c-promises/overview
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Promises
{
public interface Promise
{
Promise Done(Action callback);
Promise Fail(Action callback);
Promise Always(Action callback);
bool IsRejected { get; }
bool IsResolved { get; }
bool IsFulfilled { get; }
}
public interface Promise<T> : Promise
{
Promise<T> Done(Action<T> callback);
Promise<T> Done(IEnumerable<Action<T>> callbacks);
Promise<T> Fail(Action<T> callback);
Promise<T> Fail(IEnumerable<Action<T>> callbacks);
Promise<T> Always(Action<T> callback);
Promise<T> Always(IEnumerable<Action<T>> callbacks);
}
public class Deferred : Deferred<object>
{
// generic object
}
public class Deferred<T> : Promise<T>
{
private List<Callback> callbacks = new List<Callback>();
protected bool _isResolved = false;
protected bool _isRejected = false;
private T _arg;
public static Promise When(IEnumerable<Promise> promises)
{
var count = 0;
var masterPromise = new Deferred();
foreach (var p in promises)
{
count++;
p.Fail(() =>
{
masterPromise.Reject();
});
p.Done(() =>
{
count--;
if (0 == count)
{
masterPromise.Resolve();
}
});
}
return masterPromise;
}
public static Promise When(object d)
{
var masterPromise = new Deferred();
masterPromise.Resolve();
return masterPromise;
}
public static Promise When(Deferred d)
{
return d.Promise();
}
public static Promise<T> When(Deferred<T> d)
{
return d.Promise();
}
public Promise<T> Promise()
{
return this;
}
public Promise Always(Action callback)
{
if (_isResolved || _isRejected)
callback();
else
callbacks.Add(new Callback(callback, Callback.Condition.Always, false));
return this;
}
public Promise<T> Always(Action<T> callback)
{
if (_isResolved || _isRejected)
callback(_arg);
else
callbacks.Add(new Callback(callback, Callback.Condition.Always, true));
return this;
}
public Promise<T> Always(IEnumerable<Action<T>> callbacks)
{
foreach (Action<T> callback in callbacks)
this.Always(callback);
return this;
}
public Promise Done(Action callback)
{
if (_isResolved)
callback();
else
callbacks.Add(new Callback(callback, Callback.Condition.Success, false));
return this;
}
public Promise<T> Done(Action<T> callback)
{
if (_isResolved)
callback(_arg);
else
callbacks.Add(new Callback(callback, Callback.Condition.Success, true));
return this;
}
public Promise<T> Done(IEnumerable<Action<T>> callbacks)
{
foreach (Action<T> callback in callbacks)
this.Done(callback);
return this;
}
public Promise Fail(Action callback)
{
if (_isRejected)
callback();
else
callbacks.Add(new Callback(callback, Callback.Condition.Fail, false));
return this;
}
public Promise<T> Fail(Action<T> callback)
{
if (_isRejected)
callback(_arg);
else
callbacks.Add(new Callback(callback, Callback.Condition.Fail, true));
return this;
}
public Promise<T> Fail(IEnumerable<Action<T>> callbacks)
{
foreach (Action<T> callback in callbacks)
this.Fail(callback);
return this;
}
public bool IsRejected
{
get { return _isRejected; }
}
public bool IsResolved
{
get { return _isResolved; }
}
public bool IsFulfilled
{
get { return _isRejected || _isResolved; }
}
public Promise Reject()
{
if (_isRejected || _isResolved) // ignore if already rejected or resolved
return this;
_isRejected = true;
DequeueCallbacks(Callback.Condition.Fail);
return this;
}
public Deferred<T> Reject(T arg)
{
if (_isRejected || _isResolved) // ignore if already rejected or resolved
return this;
_isRejected = true;
this._arg = arg;
DequeueCallbacks(Callback.Condition.Fail);
return this;
}
public Promise Resolve()
{
if (_isRejected || _isResolved) // ignore if already rejected or resolved
return this;
this._isResolved = true;
DequeueCallbacks(Callback.Condition.Success);
return this;
}
public Deferred<T> Resolve(T arg)
{
if (_isRejected || _isResolved) // ignore if already rejected or resolved
return this;
this._isResolved = true;
this._arg = arg;
DequeueCallbacks(Callback.Condition.Success);
return this;
}
private void DequeueCallbacks(Callback.Condition cond)
{
foreach (Callback callback in callbacks)
{
if (callback.Cond == cond || callback.Cond == Callback.Condition.Always)
{
if (callback.IsReturnValue)
callback.Del.DynamicInvoke(_arg);
else
callback.Del.DynamicInvoke();
}
}
callbacks.Clear();
}
}
class Callback
{
public enum Condition { Always, Success, Fail };
public Callback(Delegate del, Condition cond, bool returnValue)
{
Del = del;
Cond = cond;
IsReturnValue = returnValue;
}
public bool IsReturnValue { get; private set; }
public Delegate Del { get; private set; }
public Condition Cond { get; private set; }
}
}
@GFoley83
Copy link

An example usage would be great!

@jareguo
Copy link

jareguo commented Apr 1, 2014

@golergka
Copy link

While iterating through the callbacks, it is possible for the callback collection to get modified. Check out the quick fix I implemented: https://gist.github.com/golergka/9347569ab1a31aaf46cd

@GFoley83
Copy link

Here is a sample usage:
https://dotnetfiddle.net/Gex2oB

@assafi
Copy link

assafi commented Nov 11, 2014

This is pretty cool. Would love to see a version of this with Task/Future.

@bladepop
Copy link

bladepop commented Dec 9, 2014

This is great, watch out for the collection getting modified error (there's a gist with a fix in the comments)
Also, there is a problem with the When function, I have came up with a little fix for this:
https://gist.github.com/bladepop/5fdd9a0da77f669d45d6

@ashleydavis
Copy link

I have an implementation of promises in C# here:

https://github.com/Real-Serious-Games/C-Sharp-Promise

With docs, unit tests and examples.

Also available on nuget: https://www.nuget.org/packages/RSG.Promise/

@NibbleByte
Copy link

This is really a bad implementation and I see many flows as I try to use it.

First: user will almost never want 1-parameter-type Promise<T>. User always needs a type for Done and different type for Fail handler. For example, on Done get the data, on Fail he just wants to return error code which is int.
And in any case Always will almost never need that parameter so it is useless.
(All this works for Javascript because parameter there is not strongly typed and can be everything, but not in C#).

This inheritance Promise<T> : Promise is also problematic because of the methods needed to implement:
Promise Done(Action callback);
Note that it returns Promise, not Promise<T> which breaks chaining (the most vital thing about promises). For example:

promise
.Fail( () => { ... } )  // This returns Promise, which doesn't have Promise<T>.Done(Action<T>); method implemented
.Done( (r) => { ... } ); // And this would cause compiler error.

The Deferred.When(..) methods are unclear what their usage is (if working at all)...?!?
The Promise() methods look like the mother of all hacks.
IsRejected and IsResolved could be easily replaced with short-cut properties: { get; private set; } to clear the code a bit.

I must admint that DequeueCallbacks() with callback.IsReturnValue is very clever idea. :)

I've heavily modified the code by having 2 different interfaces and separating the implementations.

    public interface Promise
    {
        Promise Done (Action callback);
        Promise Fail (Action callback);
        Promise Always (Action callback);

        bool IsRejected { get; }
        bool IsResolved { get; }
        bool IsFulfilled { get; }
    }

    public interface Promise<TDone, TFail>
    {   
        // NOTE: the return type is the same type which allows chaining.
        Promise<TDone, TFail> Done (Action callback);
        Promise<TDone, TFail> Fail (Action callback);
        Promise<TDone, TFail> Always (Action callback);

        Promise<TDone, TFail> Done (Action<TDone> callback);
        Promise<TDone, TFail> Done (IEnumerable<Action<TDone>> callbacks);
        Promise<TDone, TFail> Fail (Action<TFail> callback);
        Promise<TDone, TFail> Fail (IEnumerable<Action<TFail>> callbacks);
        // NOTE: there is NO Always with parameter! Useless!

        bool IsRejected { get; }
        bool IsResolved { get; }
        bool IsFulfilled { get; }
    }

This fixes all the problems described above, although it has some small drawbacks with maintainability etc. But this is conceptually more correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment