Skip to content

Instantly share code, notes, and snippets.

@benjamingr
Created August 13, 2014 08:32
Show Gist options
  • Save benjamingr/2a98d0d1d5fdc4710d07 to your computer and use it in GitHub Desktop.
Save benjamingr/2a98d0d1d5fdc4710d07 to your computer and use it in GitHub Desktop.
promise library in swift
ar topOps = Request.GetJson("http://tipranksphone.azurewebsites.net/api/Phone/GetRecentRatings")
var p = Promise.resolve(10)
var p2 = Promise.resolve("Hello")
let all = Promise<Int>.all(p, p2, topOps);
all.tap({ (a,b,c) in
print("All done \(a) \(b) \(c)");
})
//
// Promise.swift
// Promise
//
// Created by Benjamin Gruenbaum on 8/12/14.
// Copyright (c) 2014 Tipranks. All rights reserved.
//
import Foundation
public class Promise<T>{
var state:PromiseState<T,Any> = PromiseState(state: .Pending, value: nil, error: nil)
var fHandlers:[(T) -> ()] = []
var rHandlers:[(Any) -> ()] = []
func getState() -> State{
return self.state.state
}
public func value() -> T?{
return self.state.value;
}
public class func reject(t:Any) -> Promise<Any> {
let p:Promise<Any> = Promise<Any>()
p.state.state = .Rejected
p.state.error = t
return p
}
// no real need to alias anything, we do not assimilate foreign thenables
public class func resolve(t:Promise<T>) -> Promise<T>{
return t;
}
public class func resolve(t:T) -> Promise<T>{
let p:Promise<T> = Promise<T>()
p.state.state = .Fulfilled
p.state.value = t
return p
}
private class func async(fn:() -> ()) {
dispatch_async(dispatch_get_main_queue(), {
fn()
})
}
public init(resolver:((T) -> (), reject:(Any) -> ()) -> ()) {
resolver({(t:T) in
Promise.async({
self.fulfill(t)
println("Fulfill part in resolver, running handlers \(self.fHandlers.count)")
})
} ,
{ (e:Any) in
Promise.async({
self.reject(e)
})
})
}
private init(){
}
func tap(onFulfilled:(T) -> ()) -> Promise<T> {
return self.then({ onFulfilled($0); return self })
}
func then<NT>(onFulfilled:(T) -> Promise<NT>) -> Promise<NT> {
var p = Promise<NT>()
switch self.state.state {
case .Rejected:
Promise.async({ p.reject(self.state.error); return })
case .Fulfilled:
Promise.async({
let result = onFulfilled(self.state.value!)
let resolver:(nt:NT) -> () = { p.fulfill($0) }
result.tap(resolver)
result.catch( {p.reject($0); })
})
case .Pending:
fHandlers.append({ (t:T) in
let res = onFulfilled(t)
res.tap({ p.fulfill($0) })
res.catch({ p.reject($0) })
})
}
return p
}
func then<NT>(onFulfilled:(T) -> NT) -> Promise<NT> {
println("Inside then that returns a value")
var p = Promise<NT>()
switch self.state.state {
case .Rejected :
println("Rej case")
Promise.async({
p.reject(self.state.error!)
})
case .Fulfilled :
println("Ful case")
Promise.async({
let result = onFulfilled(self.state.value!)
p.fulfill(result)
})
case .Pending :
println("Pending case")
fHandlers.append({ (t:T) in
let res = onFulfilled(t)
p.fulfill(res)
})
}
return p
}
func catch<NT>(onRejected: (Any?) -> Promise<NT>) -> Promise<NT>{
var p = Promise<NT>()
switch self.state.state {
case .Pending:
rHandlers.append({ (t:Any?) in
let res = onRejected(t)
res.then({ p.fulfill($0) })
res.catch({ p.reject($0) })
})
case .Fulfilled:
Promise.async({
p.fulfill(self.state.value as NT)
})
case .Rejected:
Promise.async({
let res = onRejected(self.state.error)
res.then({ p.fulfill($0) })
res.then({ p.fulfill($0) })
})
}
return p;
}
func catch<NT>(onRejected: (Any?) -> NT) -> Promise<NT?> {
var p = Promise<NT?>()
switch self.state.state {
case .Pending:
rHandlers.append({ (t:Any?) in
let res = onRejected(t)
p.fulfill(res)
})
case .Fulfilled:
Promise.async({
p.fulfill(self.state.value as? NT)
})
case .Rejected:
Promise.async({
let res = onRejected(self.state.error)
p.fulfill(res)
})
}
return p
}
private func fulfill(val:T) {
if state.state != .Pending {
return
}
self.state.state = .Fulfilled
self.state.value = val
for handler in self.fHandlers {
Promise.async({ handler(val) })
}
self.fHandlers = []
}
private func reject(err:Any?) {
if state.state != .Pending {
return
}
assert(self.state.state != .Rejected)
self.state.state = .Rejected
self.state.error = err
for handler in self.rHandlers {
Promise.async({ handler(err) })
}
self.rHandlers = []
}
class func all<A,B,C>(a:Promise<A>,_ b:Promise<B>,_ c:Promise<C>) -> Promise<(A,B,C)> {
return Promise<(A,B,C)>({ (resolve:((A,B,C)) -> (), rej: (Any) -> ()) in
var sem = 3
a.catch(rej)
b.catch(rej)
c.catch(rej)
func oneDone(){
sem = sem - 1
if sem == 0 {
resolve((a.state.value!, b.state.value!, c.state.value!))
}
}
a.tap({ (a:A) in oneDone() })
b.tap({ (b:B) in oneDone() })
c.tap({ (c:C) in oneDone() })
})
}
class func all<A>(proms:[Promise<A>]) -> Promise<[A]> {
return Promise<[A]>({ (resolve, rej) in
var sem = proms.count
for prom in proms {
prom.catch(rej)
prom.tap({ (_:A) in
sem = sem - 1;
if sem == 0 {
resolve(proms.map({ $0.state.value!}))
}
})
}
})
}
class func all<A,B>(a:Promise<A>,_ b:Promise<B>) -> Promise<(A,B)> {
return Promise<(A,B)>({ (resolve:((A,B)) -> (), rej: (Any) -> ()) in
var sem = 2
a.catch(rej)
b.catch(rej)
a.tap({ (_:A) in
sem = sem - 1;
if sem == 0 {
resolve((a.state.value!,b.state.value!))
}
})
b.tap({ (_:B) in
sem = sem - 1;
if sem == 0 {
resolve((a.state.value!,b.state.value!))
}
})
})
}
}
struct PromiseState<T,E> {
var state:State
var value:T?
var error:E?
}
// hack for - unimplemented IR generation feature non-fixed multi-payload enum layout
enum State {
case Pending
case Fulfilled
case Rejected
}
//
// Request.swift
// Promise
//
// Created by Benjamin Gruenbaum on 8/13/14.
// Copyright (c) 2014 Tipranks. All rights reserved.
//
import Foundation
class Request {
class func Get(url:String) -> Promise<NSData?>{
println("Inside Request.get")
return Promise({ (resolve, reject) in
println("Inside Request.get inner promise ctor")
Web.Get(url, handleResponse: { (err, data) in
println("Inside Request.get - request is done")
if let error = err {
println("Inside Request.get in reject")
reject(err)
}
println("Inside Request.get in resolve")
resolve(data)
})
})
}
class func GetJson(url:String) -> Promise<AnyObject>{
return Get(url).then({ (data) -> (AnyObject) in
if let data:NSData = data {
var err : NSError?
let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &err)
if let j: AnyObject = json {
println("Inside Request.get - deserialization good")
return j
} else if let error = err{
return Promise<AnyObject>.reject(NSError(domain:"GetJson",code:0,userInfo:["Reason":"Could not deserialize JSON", "Error": err!]))
} else {
return Promise<AnyObject>.reject(NSError(domain:"GetJson",code:0,userInfo:["Reason":"JSON deserialized into nil but no error"]))
}
}
return Promise<AnyObject>.reject(NSError(domain:"GetJson",code:0,userInfo:["Reason":"Got nil response from web"]))
})
}
}
internal class Web{
class func Post(url:String,data:NSData, handleResponse:(NSError?,AnyObject?) -> ()){
var req = NSMutableURLRequest(URL: NSURL(string:url))
req.HTTPMethod = "POST"
req.HTTPBody = data
Web.Request(url, request: req, handleResponse: handleResponse)
}
class func PostJson(url:String,data:AnyObject?, handleResponse:(NSError?,NSData?) -> ()){
var req = NSMutableURLRequest(URL: NSURL(string:url))
req.HTTPMethod = "POST";
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.setValue("TipRanks iOS client", forHTTPHeaderField: "User-Agent")
var e:NSError?
let jsonData = NSJSONSerialization.dataWithJSONObject(
data,
options: NSJSONWritingOptions(0),
error: &e)
let asString = NSString(data:jsonData,encoding:NSUTF8StringEncoding)
if e != nil {
return handleResponse(e, nil)
}
req.HTTPBody = jsonData
Web.Request(url, request: req, handleResponse: handleResponse);
}
class func Request(url:String,request:NSURLRequest? = nil, handleResponse:(NSError?,NSData?) -> ()){
let req = request ?? NSURLRequest(URL: NSURL(string:url))
NSURLConnection.sendAsynchronousRequest(req, queue: NSOperationQueue.mainQueue(), completionHandler: { (resp: NSURLResponse!, data, err) in
if let error = err {
if error.description.rangeOfString("-1012") != nil{ // 401
return handleResponse(NSError(domain: "WebDomain", code: 401, userInfo: ["Data":data, "Error": error]),nil)
}
return handleResponse(error, nil)
}
if let httpResp = resp as? NSHTTPURLResponse {
if httpResp.statusCode >= 400 {
handleResponse(NSError(domain: "WebDomain", code: httpResp.statusCode, userInfo: ["Data":data]), nil)
}
if let data:NSData = data {
handleResponse(nil, data)
} else {
handleResponse(err, nil)
}
} else {
handleResponse(NSError(domain: "WebDomain", code: 404, userInfo: ["Reason":"Web Request error, could not cast to NSHTTPURLResponse"]), nil)
}
})
}
class func Get(url:String,handleResponse:(NSError?, NSData?) -> ()){
Request(url, handleResponse:handleResponse)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment