Pages

uPromise

Download | Documentation | Change Log

uPromise - Promises Pattern library for Unity (C#), inspired by q.js (for javascript).
Promises are a software abstraction that provides a great way for working with asynchronous code and also provides an abstraction between synchronous and asynchronous functions.





Highlights

  • Fluent API / Chaining
  • Statically Typed
  • Develop sync/async easier and better
  • Async and Sync abstraction
  • Promises can mitigate the "Pyramid of Doom", meaning having nested lambdas in each other.
  • Includes several examples

Supported Platforms:
Windows, Android, iOS, Web (ask to ensure on others if needed)

Unity supported:
Unity, Unity Pro

Requires Unity 3.5.0 or higher.


Download

You can get it from the unity asset store https://www.assetstore.unity3d.com/#/content/15604.

Documentation

Promise: a proxy object for a result that is initially unknown.
Deferred: the creator of the promise, and will change the result of the promise once the operation has been completed.

What are they used for?

 - They replace tradition callbacks e.g. http.Get(onDone, onFail)
 - Commonly used with data services for HTTP, but can used in any async form.
 - Promises can mitigate the "Pyramid of Doom", meaning having nested lambdas in each other.

FAQ:

 Q: I'm resolving/rejecting a promise and the .Fail/.Then on the subscribers are not being invoked.
 A: You are resolving the deferred before returning the promise, make sure that the "Resolving" will be done after (async) returning the promise or see "Creating a Promise (resolved) from non-async" if needed.

Creating a simple Deferred<T>

// Create a new deferred.
var deferred = new Deferred<Person>();

// Call an async operation, and pass deferred as parameter.
// after async operation has been complete invoke either deferred.Resolve(new Person{ Name = "Joe"}) or deferred.Reject();
// IMPORTANT NOTE: if the operation is not async see: "Creating a Promise from non-async"
// THE HttpClient IS NOT INCLUDED WITHIN THIS PACKAGE - ITS JUST AN EXAMPLE.
HttpClient.Get<Person>(onDone: response => deferred.Resolve(response.Content), onFail: error => deferred.Reject(error)  

// Return the Promise.
return deferred.Promise;

Resolving a deferred

// Once the async operation finishes, Simply Resolve by calling: 
deferred.Resolve()
OR
deferred.Resolve(arg) e.g. deferred.Resolve(new Person{Name = "Joe"});

After resolving the deferred, it will cause the promise to fire the .Then for all its subscribers and the "arg" value will be passed as a parameter to it.

Rejecting a deferred

// Once the async operation finishes, Simply Reject by calling: 
deferred.Reject() //when no reason need to be returned and just cause the promise to fail.
OR
deferred.Reject(error) e.g. deferred.Reject(System.Net.HttpStatusCode.BadRequest);

After rejecting the deferred, it will cause the promise to fire the .Fail for all its subscribers and the "error" value will be passed as a parameter to it.

Notify a promise

// Once the async operation is going, Simply Notify by calling: 
deferred.Notify(arg) e.g. deferred.Notify("enquiring data");

After notify the promise, it will cause the promise to fire the .Progress for all its subscribers and the "arg" value will be passed as a parameter to it.

Creating a Promise (resolved) from non-async

TaskFactory.StartNew(x => new Person{ Name = "Joe"});
OR 
// create a promise with delay (seconds), which will be resolved after the delay parameter.
TaskFactory.StartNew(x => new Person{ Name = "Joe"}, delay);

Promise: Then

Invoked after the Deferred has been resolved, with parameter "x" being the resolved arg.
This is similar to .Done, but with some key differences.

  • .Then which are subscribed will be executed sequentially, if one of them fails, immediately exit (without invoking any others) and invoke .Fail.
  • .Then is capable of changing the promise into a new promise
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

personService.Get(id: 123)
 .Then(x => 
 {
  Debug.Log(string.Format("Person.Name={0}", x.Name));
 });
 
// Changing Promise by Then Example
personService.Get(id: 123)
 .Then(x => 
 {
  Debug.Log(string.Format("Person.Name={0}", x.Name));
  return x.Name; //this will change the promise from Promise<Person> to Promise<string>.
 }); 

Promise: Done

Invoked after the Deferred has been fully resolved, with parameter "x" being the resolved arg.
By fully resolved means, all .Then subscribed has been finished successful.
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

personService.Get(id: 123)
 .Done(x => 
 {
  Debug.Log(string.Format("Person.Name={0}", x.Name));
 });

Promise: Fail

Fail will be invoked in different ways:
  •  Deferred has been rejected e.g. deferred.Reject()
  •  An error has occurred during .Then()
This is very similar to try/catch, were Fail being the catch.
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

// This will catch all Fail's of any type.
personService.Get(id: 123)
 .Fail(x => 
 {  
  Debug.Log(string.Format("An error has occurred. x={0}", x));
 });

 
// This will catch ONLY Fail's which are of specific type: System.Net.HttpStatusCode
personService.Get(id: 123)
 .Fail<System.Net.HttpStatusCode>(x => 
 {  
  Debug.Log(string.Format("An error has occurred. x={0}", x));
 });

Promise: Finally

This is very similar to try/catch/finally, were Finally being the finally, meaning it will be invoked either when a promise is fully resolved (e.g. not when .Then is complete but all the chains are complete) or when it fails.
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

personService.Get(id: 123)
 .Finally(() => 
 {  
  Debug.Log("Promise ends.");
 });

Promise: Progress

Invoked by calling Notify on the Deferred, with parameter "x" being the .Notify arg.
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

personService.Get(id: 123)
 .Progress<string>(x => 
 {
  Debug.Log(string.Format("The progress status={0}", x))
 });

Promise Method Chaining

Promise methods can be chained together
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

personService.Get(id: 123)
 .Then(x => //x is Person
 {
  Debug.Log(string.Format("Person.Name={0}", x.Name));
  return x.Name; //this will change the promise from Promise<Person> to Promise<string>
 })
 .Then(x => //x is string because of the above.
 {
  Debug.Log(string.Format("x={0}", x));  
 })
 .Progress<string>(x => Debug.Log(string.Format("The progress status={0}", x)))
 .Fail<System.Net.HttpStatusCode>(x => Debug.Log(string.Format("An error has occurred. x={0}", x)))
 .Fail(x => Debug.Log(string.Format("An error has occurred. x={0}", x)))
 .Finally(() => Debug.Log("Promise ends."));

Promise.All

Wait for all promises specified to be resolved (parallel) in order for the All Promise to be resolved, if any of them fails, it will immediately reject the promise.
// Assuming that personService has a method .Get(id) which returns a Promise<Person>

var personPromise = personService.Get(id: 123);
var personPromise2 = personService.Get(id: 2);

Promise.All(personPromise, personPromise2)
    .Then(x =>
          {
           Debug.Log(string.Format("All Sample [Then] all complete. result. x[0]={0} x[1]={1}", x));
           return (Person) x[0]; // return first value which is the first person.
          })

Change Log

---------------------------------------
 v1.4.0 - 14/05/2014
---------------------------------------
Change: 
 - Implemented Promise.Reject and Promise.Reject<T> which allows you to simply return a rejected promise.
usage: return Promise.Reject(PlayerQuestErrorState.QuestLogFull);
 - Implemented Then parameterless.
 - Implemented Promise All Sequentially, like Promise.All but they will run one after the other.
usage: Promise.AllSequentially(Current.Shutdown, sceneControllerTo.Initialize).
Break Changes:
 - Done has been changed: It will now return void which can be used at the end of the chain.
 - TaskFactory has been renamed to PromiseFactory.
 Fix:
 - Fixed an issue on TaskFactory.StartNewDelayed which caused it to never resolve.
 - TaskFactory cancellation is now passed properly to functions from signatures.

---------------------------------------
 v1.3.2 - 23/04/2014
---------------------------------------
 - Then Promise chaining (non generic support).
 - Fixes in Finally when using Promise<T>, and an error occurs it was causing it to crash.

---------------------------------------
 v1.3.1 - 19/03/2014
---------------------------------------
 - Cleaned several Debug.Logs

---------------------------------------
 v1.2 - 06/03/2014
---------------------------------------
 - Implemented Promise<T>.Fail(Action<object>) which was missing to match Promise (non generic)
 - Implemented Promise<T>.Finally(Action) which was missing to match Promise (non generic)
 - TaskFactory:
- Created StartNewDeferred which will create a new deferred without being automatically resolved. 
- Recreated methods to ensure both StartNew for Promise<T> and Promise will have exact same functionality and signatures.
- Refactoring

---------------------------------------
 v1.1 - 27/02/2014
---------------------------------------
 - Reworked .Fail method to have a better API.
- Implemented .Fail<TFail> method which listens only to the specified type.
- .Fail<object> and .Fail are now triggered always when failed.
- Implemented Reject<TFail>(TFail arg) which allows fail for specified type.

---------------------------------------
 v1.0 - 24/02/2014
---------------------------------------
initial release

No comments:

Post a Comment