Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Promise of an API

620 views

Published on

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.

Published in: Engineering, Technology
  • Be the first to like this

Promise of an API

  1. 1. Promise of an API Maxim Zaks (@iceX33) CocoaHeads Berlin
  2. 2. Agenda — Consume a promise — Produce a promise — Swift
  3. 3. - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
  4. 4. - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2; - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error;
  5. 5. - (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;
  6. 6. - (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;
  7. 7. And now with a real promise! - (OMPromise*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
  8. 8. What is a promise (OMPromise)? It's just an object
  9. 9. It has state — Unfulfilled — Fulfilled — Failed
  10. 10. It has other properties — result — error — progress*
  11. 11. It has methods - (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler; - (OMPromise *)failed:(void (^)(NSError *error))failHandler; - (OMPromise *)progressed:(void (^)(float progress))progressHandler;
  12. 12. 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;
  13. 13. So let's see it in action
  14. 14. OMPromise *sum = [calc addFirstNumber:@3 toSecondNumber:@2]; [[sum fulfilled:^(id result){ NSLog(@"Sum: %@", result); }] failed:^(NSError *error){ NSLog(@"Failed to calculate because of: %@", error); }];
  15. 15. Concatenation sum1 = 3 + 2 sum2 = sum1 + 2 sum3 = sum1 + 3
  16. 16. 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()];
  17. 17. 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];
  18. 18. Recovering from failed promise OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2]; OMPromise *rescuedSum = [sum1 rescue:^(NSError *error){ NSLog(@"Shit happens! %@", error); return @0; }];
  19. 19. 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;
  20. 20. 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;
  21. 21. Recap 3/3 + (OMPromise *)chain:(NSArray *)thenHandlers initial:(id)result; + (OMPromise *)any:(NSArray *)promises; + (OMPromise *)all:(NSArray *)promises;
  22. 22. Now what's about creating a promise?
  23. 23. Convenience methods on OMPromise + (OMPromise *)promiseWithResult:(id)result; + (OMPromise *)promiseWithResult:(id)result after:(NSTimeInterval)delay; + (OMPromise *)promiseWithError:(NSError *)error; + (OMPromise *)promiseWithTask:(id (^)())task;
  24. 24. Example for success - (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 { return [OMPromise promiseWithTask:^{ return @([n1 intValue] + [n2 intValue]) }]; }
  25. 25. Example for error - (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 { return [OMPromise promiseWithTask:^{ return [NSError new]; }]; }
  26. 26. And what is the non convenient way?
  27. 27. Defered object @interface OMDeferred : OMPromise + (OMDeferred *)deferred; - (OMPromise *)promise; - (void)fulfil:(id)result; - (void)fail:(NSError *)error; - (void)progress:(float)progress; @end
  28. 28. 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; }
  29. 29. Recap — Promise is an immutable data structure — Deferred is a subclass of Promise which makes it mutable
  30. 30. To add up confusion I implemented promises in Swift
  31. 31. First attempt was just to port it
  32. 32. Using Enums for state representation feels right enum PromiseState{ case Unfulfilled case Fulfilled case Failed }
  33. 33. I need inheritance so let's use classes
  34. 34. 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 }
  35. 35. 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 } }
  36. 36. 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 } }
  37. 37. Sadly this was an infinite loop — There are no Read-Only properties in Swift — Only Read-Only Computed Properties
  38. 38. Schock number 2 — There are no ivars in Swift — And no visibility constrains (but they promised to introduced those)
  39. 39. After initial shock pass, I decided to go functional
  40. 40. 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()) } } } } }
  41. 41. Main concept = currying func add(n1:Int)(n2:Int) -> Int { return n1 + n2 } let increaseByTwo = add(2) let sum = increaseByTwo(3)
  42. 42. 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) }
  43. 43. Here is how you can use it: var f = future { FutureResult.Result(2 + 3) } f (handler: FutureHandler.FulFilled { result in println("Promise fulfilled : (result)") })
  44. 44. It's not a beauty but it works
  45. 45. However you can't concatenate futures in this implementation — typealias F = (FutureHandler<Int>) -> F
  46. 46. For the tough ones, who survived this talk: — https://github.com/b52/OMPromises — https://github.com/mzaks/siwft-future
  47. 47. Thank you! Maxim Zaks (@iceX33)

×