promise of an api
DESCRIPTION
This presentation was held at CocoaHeads Berlin. I am explaining the concept of promise with help of OMPromise Library written in ObjC. In the end I present a non complete Swift implementation of Promises concept that I hacked together in couple of hours.TRANSCRIPT
Promise of an APIMaxim Zaks (@iceX33)CocoaHeads Berlin
Agenda
— Consume a promise
— Produce a promise
— Swift
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error;
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error;
- (void)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withBlock:(void (^)(NSNumber *sum, NSError *error))block;
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error;
- (void)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withBlock:(void (^)(NSNumber *sum, NSError *error))block;
- (void)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 success:(void (^)(NSNumber *sum))success failure:(void (^)(NSError *error))failur;
And now with a real promise!
- (OMPromise*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
What is a promise (OMPromise)?It's just an object
It has state
— Unfulfilled
— Fulfilled
— Failed
It has other properties
— result
— error
— progress*
It has methods
- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler;- (OMPromise *)failed:(void (^)(NSError *error))failHandler;- (OMPromise *)progressed:(void (^)(float progress))progressHandler;
It lets you call handlers on a custom queue
- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler on:(dispatch_queue_t)queue;- (OMPromise *)failed:(void (^)(NSError *error))failHandler on:(dispatch_queue_t)queue;- (OMPromise *)progressed:(void (^)(float progress))progressHandler on:(dispatch_queue_t)queue;
So let's see it in action
OMPromise *sum = [calc addFirstNumber:@3 toSecondNumber:@2];[[sum fulfilled:^(id result){ NSLog(@"Sum: %@", result);}] failed:^(NSError *error){ NSLog(@"Failed to calculate because of: %@", error);}];
Concatenation
sum1 = 3 + 2sum2 = sum1 + 2sum3 = sum1 + 3
Concatenation
OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2];OMPromise *sum2 = [sum1 than:^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@2];}];OMPromise *sum3 = [sum1 than:^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@3];}];
[[[OMPromise all:@[sum1, sum2, sum3]] fulfilled:^(NSArray *results){ NSLog(@"Sum1: %@, Sum2: %@, Sum3: %@", results[0], results[1], results[2]);} on:dispatch_get_main_queue()] failed:^(NSError *error){ NSLog(@"Failed to calculate because of: %@", error);} on:dispatch_get_main_queue()];
Deep chaining
OMPromise *sum = [OMPromise chain:[ ^(NSNumber *n1){ return [calc addFirstNumber:@2 toSecondNumber:@3]; }, ^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@3]; }, ^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@2]; }] initial:nil];
Recovering from failed promise
OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2];OMPromise *rescuedSum = [sum1 rescue:^(NSError *error){ NSLog(@"Shit happens! %@", error); return @0; }];
Recap 1/3
@interface OMPromise : NSObject
@property(assign, readonly, nonatomic) OMPromiseState state;@property(readonly, nonatomic) id result;@property(readonly, nonatomic) NSError *error;@property(assign, readonly, nonatomic) float progress;
Recap 2/3
- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler;- (OMPromise *)failed:(void (^)(NSError *error))failHandler;- (OMPromise *)progressed:(void (^)(float progress))progressHandler;
- (OMPromise *)then:(id (^)(id result))thenHandler;- (OMPromise *)rescue:(id (^)(NSError *error))rescueHandler;
Recap 3/3
+ (OMPromise *)chain:(NSArray *)thenHandlers initial:(id)result;+ (OMPromise *)any:(NSArray *)promises;+ (OMPromise *)all:(NSArray *)promises;
Now what's about creating a promise?
Convenience methods on OMPromise
+ (OMPromise *)promiseWithResult:(id)result;+ (OMPromise *)promiseWithResult:(id)result after:(NSTimeInterval)delay;+ (OMPromise *)promiseWithError:(NSError *)error;
+ (OMPromise *)promiseWithTask:(id (^)())task;
Example for success
- (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2{ return [OMPromise promiseWithTask:^{ return @([n1 intValue] + [n2 intValue]) }];}
Example for error
- (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2{ return [OMPromise promiseWithTask:^{ return [NSError new]; }];}
And what is the non convenient way?
Defered object
@interface OMDeferred : OMPromise+ (OMDeferred *)deferred;- (OMPromise *)promise;- (void)fulfil:(id)result;- (void)fail:(NSError *)error;- (void)progress:(float)progress;@end
Example
- (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2{ OMDeferred *deferred = [OMDeferred deferred];
dispatch_async(dispatch_get_global_queue(0, 0), ^ { NSError *error = nil; NSNumber *sum = [self addFirstNumber:number1 toSecondNumber:number2 withError:&error]; if(error){ [deferred fail:error]; } else { [deferred fulfill:sum]; } }); return deferred.promise;}
Recap
— Promise is an immutable data structure
— Deferred is a subclass of Promise which makes it mutable
To add up confusion I implemented promises in Swift
First attempt was just to port it
Using Enums for state representation feels right
enum PromiseState{ case Unfulfilled case Fulfilled case Failed}
I need inheritance so let's use classes
Using constants as read only property is not a good idea as you have to set them directly in the initializer
class Promise<T> { let state : PromiseState
let result : T
let error : NSError}
Switching to var makes it work but Promise is not immutable any more
class Promise<T> { var state : PromiseState
var result : T?
var error : NSError?
init(){ state = .Unfulfilled }
func fulfill(value : T){ result = value state = .Fulfilled }}
Trying to make a read only property
class Promise<T> { var state : PromiseState
var result : T? { get { return self.result } }
var error : NSError?
init(){ state = .Unfulfilled }
}
Sadly this was an infinite loop
— There are no Read-Only properties in Swift
— Only Read-Only Computed Properties
Schock number 2
— There are no ivars in Swift
— And no visibility constrains (but they promised to introduced those)
After initial shock pass, I decided to go functional
func future<T>(execution : ()->FutureResult<T>)(handler: FutureHandler<T>) { var result : FutureResult<T>? var done = false dispatch_async(dispatch_get_global_queue(0, 0)) { result = execution() done = true } dispatch_async(dispatch_get_global_queue(0, 0)) { while !done {} dispatch_async(dispatch_get_main_queue()) { switch handler { case .FulFilled(let fulfilled): switch result! { case .Result(let value): fulfilled(value()) case .Error : println("can't process error") } case .Failed(let failed): switch result! { case .Result: println("can't process result") case .Error(let error) : failed(error()) } } } }}
Main concept = currying
func add(n1:Int)(n2:Int) -> Int { return n1 + n2}
let increaseByTwo = add(2)
let sum = increaseByTwo(3)
Using enums with Associated Values
enum FutureHandler<T>{ case FulFilled((T)->()) case Failed((NSError)->())}
enum FutureResult<T>{ case Result(@auto_closure ()->T) case Error(@auto_closure ()->NSError)}
Here is how you can use it:
var f = future { FutureResult.Result(2 + 3)}
f (handler: FutureHandler.FulFilled { result in println("Promise fulfilled : \(result)")})
It's not a beauty but it works
However you can't concatenate futures in this implementation
— typealias F = (FutureHandler<Int>) -> F
For the tough ones, who survived this talk:
— https://github.com/b52/OMPromises
— https://github.com/mzaks/siwft-future
Thank you!
Maxim Zaks (@iceX33)