1

I am using C# Promises library for an Unity project, and I want to call a block of promise code indefinite times. Each time the promise is resolved it should decide whether it should be called again or not. I found this JavaScript solution for ES6 in the thread JavaScript ES6 promise for loop.

// JavaScript: Function calling itself upon resolution
(function loop(i) {
    if (i < 10) new Promise((resolve, reject) => {
        setTimeout( () => {
            console.log(i);
            resolve();
        }, Math.random() * 1000);
    }).then(loop.bind(null, i+1));
})(0);

Could you help me to port this idea into C#?

What I am currently doing is forming a long sequence of promises using Then() in a for loop, and rejecting when I want to exit. But that is a weak solution. Instead, I want to only consider about the next iteration when the current promise is resolved.

// C#: Forming the sequence before execution
var p = Promise.Resolved();
for (var i = 0; i < 100; i++)
{
    p = p.Then(outcome =>
    {
        if (shouldContinue)
        {
            // return a new Promise
        }
        else
        {
            // return Promise.Reject()
        }
    });
}

Edit:

Please take a look at my answer below for the PromiseLooper class.

nipunasudha
  • 2,427
  • 2
  • 21
  • 46
  • You've obviously seen TFM - https://github.com/Real-Serious-Games/C-Sharp-Promise#chaining-async-operations (as you've linked to it in the question). Could you clarify why that approach did not work for you? – Alexei Levenkov Feb 25 '19 at 05:03
  • Thank you for the response @AlexeiLevenkov. I want to chain a dynamic number of promises. But the number of promises cannot be decided at the initialization of the sequence. It depends on the results of the previous promises. The number of loops can be 5 or 1000. – nipunasudha Feb 25 '19 at 05:17

3 Answers3

0

First Define your function for 'then' separately. Then make sure your promise variable is accessible from the function. Finally, set your promise.then inside that function instead of outside.

This is an example in VB.net. (shouldn't be hard to change to c#)

class Test
    Dim p As RSG.Promise = RSG.Promise.Resolved
    Private Function AndThen() As RSG.Promise
    '//do something ...
        If True Then
            Threading.Thread.Sleep(10000)
            MsgBox("AndThen")
            '//setup next promise here.
            p = p.Then(AddressOf AndThen)
            Return p
        Else
            Return RSG.Promise.Rejected(New Exception)
        End If
    End Function
    Public Sub Test()
        '//our only external call of promise.then
        p.Then(AddressOf AndThen)
    End Sub
End Class
nipunasudha
  • 2,427
  • 2
  • 21
  • 46
Wolf-Kun
  • 679
  • 6
  • 12
0

If I'd need loop with asynchronous methods I'd try to find "async support for Unity3d" like this.

Otherwise you need to chain "repeat one less time" similar how it is done in JS solution you've shown. I'm not sure of exact syntax (as I don't have Unity3d) but roughly it should be

Promise Loop(
      Func<Promise<TResult>> body, 
      Func<bool, TResult> shouldContinue,
      int iterations, Promise chain = null)
{
     chain = chain == null ?? Promise.Resolved() : chain;

     if (iterations == 0)
     { 
           return Promise.Resolved();
     }  

     Promise result = body().Then(outcome => 
     {
         if (shouldContinue(outcome))
             return Loop(body, iterations-1, result));
         else 
             return Promise.Rejected();
     });
}
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
0

I came up with a cute generic PromiseLooper. This class allows

  • Providing a Func<IPromise<T>> method to loop
  • Providing Func<T,bool> a predicate to exit the loop
  • Chaining another promise onResolve of the loop
  • Catching exceptions thrown in the looping method

Here is the PromiseLooper class:

// See https://stackoverflow.com/q/54859305/4112088
public class PromiseLooper<T>
{
    private readonly Func<IPromise<T>> loopingBody;
    private readonly Func<T, bool> continuePredicate;
    private readonly Exception exitException;

    // looper core method
    private IPromise<T> Loop(T previousOutcome)
    {
        return continuePredicate(previousOutcome)
            ? loopingBody().Then(Loop)
            : Promise<T>.Rejected(exitException);
    }

    // constructor
    public PromiseLooper(Func<IPromise<T>> loopingBody,
        Func<T, bool> continuePredicate)
    {
        this.loopingBody = loopingBody;
        this.continuePredicate = continuePredicate;
        // setting up a exit exception
        this.exitException = new Exception("LooperExit");
    }

    // looping starts when this called
    public IPromise StartLooping(T initialOutcome)
    {
        var loopPromise = new Promise();
        // reporting back loop status as a promise
        Loop(initialOutcome).Catch(e =>
        {
            if (e == exitException) loopPromise.Resolve();
            else loopPromise.Reject(e);
        });
        return loopPromise;
    }
}

Example usage:

// where you create the looper and call StartLooping()
private void Start()
{
    var looper = new PromiseLooper<int>(GenerateRandomInt, IsNotFive);
    looper.StartLooping(0).Then(
        () => Console.WriteLine("Loop complete!"),
        e => Console.WriteLine($"Loop error! {e}"));
}

// the predicate that decides the end of the loop
private bool IsNotFive(int x)
{
    return x != 5;
}

// the method you want to loop!
private IPromise<int> GenerateRandomInt()
{
    var promise = new Promise<int>();
    //this could be any async time consuming call
    // that resolves the promise with the outcome
    LeanTween.delayedCall(1,
        () => promise.Resolve(Random.Range(0, 10))
    );
    return promise;
}
nipunasudha
  • 2,427
  • 2
  • 21
  • 46