promise of an api

48
Promise of an API Maxim Zaks (@iceX33) CocoaHeads Berlin

Upload: maxim-zaks

Post on 10-May-2015

405 views

Category:

Engineering


6 download

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

Page 1: Promise of an API

Promise of an APIMaxim Zaks (@iceX33)CocoaHeads Berlin

Page 2: Promise of an API

Agenda

— Consume a promise

— Produce a promise

— Swift

Page 3: Promise of an API

- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;

Page 4: Promise of an API

- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;

- (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error;

Page 5: Promise of an API

- (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;

Page 6: Promise of an API

- (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;

Page 7: Promise of an API

And now with a real promise!

- (OMPromise*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;

Page 8: Promise of an API

What is a promise (OMPromise)?It's just an object

Page 9: Promise of an API

It has state

— Unfulfilled

— Fulfilled

— Failed

Page 10: Promise of an API

It has other properties

— result

— error

— progress*

Page 11: Promise of an API

It has methods

- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler;- (OMPromise *)failed:(void (^)(NSError *error))failHandler;- (OMPromise *)progressed:(void (^)(float progress))progressHandler;

Page 12: Promise of an API

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;

Page 13: Promise of an API

So let's see it in action

Page 14: Promise of an API

OMPromise *sum = [calc addFirstNumber:@3 toSecondNumber:@2];[[sum fulfilled:^(id result){ NSLog(@"Sum: %@", result);}] failed:^(NSError *error){ NSLog(@"Failed to calculate because of: %@", error);}];

Page 15: Promise of an API

Concatenation

sum1 = 3 + 2sum2 = sum1 + 2sum3 = sum1 + 3

Page 16: Promise of an API

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()];

Page 17: Promise of an API
Page 18: Promise of an API

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];

Page 19: Promise of an API

Recovering from failed promise

OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2];OMPromise *rescuedSum = [sum1 rescue:^(NSError *error){ NSLog(@"Shit happens! %@", error); return @0; }];

Page 20: Promise of an API

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;

Page 21: Promise of an API

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;

Page 22: Promise of an API

Recap 3/3

+ (OMPromise *)chain:(NSArray *)thenHandlers initial:(id)result;+ (OMPromise *)any:(NSArray *)promises;+ (OMPromise *)all:(NSArray *)promises;

Page 23: Promise of an API

Now what's about creating a promise?

Page 24: Promise of an API

Convenience methods on OMPromise

+ (OMPromise *)promiseWithResult:(id)result;+ (OMPromise *)promiseWithResult:(id)result after:(NSTimeInterval)delay;+ (OMPromise *)promiseWithError:(NSError *)error;

+ (OMPromise *)promiseWithTask:(id (^)())task;

Page 25: Promise of an API

Example for success

- (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2{ return [OMPromise promiseWithTask:^{ return @([n1 intValue] + [n2 intValue]) }];}

Page 26: Promise of an API

Example for error

- (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2{ return [OMPromise promiseWithTask:^{ return [NSError new]; }];}

Page 27: Promise of an API

And what is the non convenient way?

Page 28: Promise of an API

Defered object

@interface OMDeferred : OMPromise+ (OMDeferred *)deferred;- (OMPromise *)promise;- (void)fulfil:(id)result;- (void)fail:(NSError *)error;- (void)progress:(float)progress;@end

Page 29: Promise of an API

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;}

Page 30: Promise of an API

Recap

— Promise is an immutable data structure

— Deferred is a subclass of Promise which makes it mutable

Page 31: Promise of an API

To add up confusion I implemented promises in Swift

Page 32: Promise of an API

First attempt was just to port it

Page 33: Promise of an API

Using Enums for state representation feels right

enum PromiseState{ case Unfulfilled case Fulfilled case Failed}

Page 34: Promise of an API

I need inheritance so let's use classes

Page 35: Promise of an API

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}

Page 36: Promise of an API

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 }}

Page 37: Promise of an API

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 }

}

Page 38: Promise of an API

Sadly this was an infinite loop

— There are no Read-Only properties in Swift

— Only Read-Only Computed Properties

Page 39: Promise of an API

Schock number 2

— There are no ivars in Swift

— And no visibility constrains (but they promised to introduced those)

Page 40: Promise of an API

After initial shock pass, I decided to go functional

Page 41: Promise of an API

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()) } } } }}

Page 42: Promise of an API

Main concept = currying

func add(n1:Int)(n2:Int) -> Int { return n1 + n2}

let increaseByTwo = add(2)

let sum = increaseByTwo(3)

Page 43: Promise of an API

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)}

Page 44: Promise of an API

Here is how you can use it:

var f = future { FutureResult.Result(2 + 3)}

f (handler: FutureHandler.FulFilled { result in println("Promise fulfilled : \(result)")})

Page 45: Promise of an API

It's not a beauty but it works

Page 46: Promise of an API

However you can't concatenate futures in this implementation

— typealias F = (FutureHandler<Int>) -> F

Page 47: Promise of an API

For the tough ones, who survived this talk:

— https://github.com/b52/OMPromises

— https://github.com/mzaks/siwft-future

Page 48: Promise of an API

Thank you!

Maxim Zaks (@iceX33)