Created
June 9, 2014 00:56
-
-
Save DonaldHays/f20fdff9b410e378b164 to your computer and use it in GitHub Desktop.
A basic Promise implementation in Swift that takes a worker and supports "then" and "done" methods. The worker runs on a background queue. The callbacks to "then" and "done" run on the main queue. I give no guarantees that this is actually production-quality :D
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Promise.swift | |
// | |
// Created by Donald Hays on 6/4/14. | |
// | |
import Foundation | |
/** | |
* A Promise is a container of a single value. That value will be assigned only | |
* once, and the assignment will happen at some point in time after the Promise | |
* has been created. Code that receives a Promise can subscribe to be notified | |
* when the assignment happens. | |
*/ | |
class Promise: NSObject { | |
enum State { | |
case Pending | |
case Resolved | |
case Rejected | |
} | |
var _state = State.Pending | |
var _value: Any = nil | |
var _error: NSError? = nil | |
var _handlers: (onFulfill: (value: Any) -> Void, onReject: (error: NSError) -> Void)[] = [] | |
/** | |
* Returns a Promise object that'll execute a given worker. The worker will | |
* automatically be invoked on a background queue. The worker must call | |
* either resolve or reject, it must call only one of them, and it must make | |
* the call only once. Resolve and reject do not need to be called from the | |
* same queue nor call stack as the worker. | |
*/ | |
init(worker: (resolve: (value: Any) -> Void, reject: (error: NSError) -> Void) -> Void) { | |
super.init() | |
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { | |
worker(resolve: { | |
value in | |
dispatch_async(dispatch_get_main_queue()) { | |
self._transition(Promise.State.Resolved, value: value, error: nil) | |
} | |
}, reject: { | |
error in | |
dispatch_async(dispatch_get_main_queue()) { | |
self._transition(Promise.State.Rejected, value: nil, error: error) | |
} | |
}) | |
} | |
} | |
// MARK: Private API | |
func _transition(state: State, value: Any, error: NSError?) { | |
if self._state == State.Pending { | |
if state == State.Resolved { | |
self._value = value | |
self._state = State.Resolved | |
for handler in self._handlers { | |
self._handle(handler) | |
} | |
} else if state == State.Rejected { | |
self._error = error | |
self._state = State.Rejected | |
for handler in self._handlers { | |
self._handle(handler) | |
} | |
} else { | |
NSException(name: NSGenericException, reason: "Attempt to transition promise from pending to pending states", userInfo: nil).raise() | |
} | |
} else { | |
NSException(name: NSGenericException, reason: "Attempt to transition promise that has already transitioned", userInfo: nil).raise() | |
} | |
} | |
func _handle(handler: (onFulfill: (value: Any) -> Void, onReject: (error: NSError) -> Void)) { | |
switch _state { | |
case .Pending: | |
_handlers.append(handler) | |
case .Resolved: | |
dispatch_async(dispatch_get_main_queue()) { | |
handler.onFulfill(value: self._value) | |
} | |
case .Rejected: | |
dispatch_async(dispatch_get_main_queue()) { | |
handler.onReject(error: self._error!) | |
} | |
} | |
} | |
// MARK: Public API | |
/** | |
* Subscribes functions to be notified when the receiver resolves. Functions | |
* can be subscribed to either success or failure conditions. Both functions | |
* are optional. If the receiver has already resolved by the time `then` is | |
* called, the corresponding function will automatically invoke immediately. | |
* The functions will invoke on the main queue. This method returns a | |
* Promise. | |
* | |
* The functions must return a value. They can return a Promise, an NSError, | |
* or some other value. If they return a Promise, the Promise that `then` | |
* returns will instead resolve when the returned Promise resolves. If they | |
* return an NSError, the Promise that `then` returns will reject with the | |
* error. If they return any other value, the Promise that `then` returns | |
* will resolve with that value. | |
* | |
* In general, you should favor the `done` method over `then`. Only call | |
* `then` if you intend to invoke `then` or `done` on the returned Promise. | |
*/ | |
func then(resolved: ((value: Any) -> Any)? = nil, rejected: ((error: NSError) -> Any)? = nil) -> Promise { | |
return Promise(worker: { | |
resolve, reject in | |
func handleResult(result: Any) { | |
if result is Promise { | |
var promise = result as Promise | |
promise.then(resolved: { | |
result in | |
resolve(value: result) | |
return result | |
}, rejected: { | |
error in | |
reject(error: error) | |
return error | |
}) | |
} else if result is NSError { | |
reject(error: result as NSError) | |
} else { | |
resolve(value: result) | |
} | |
} | |
func onFulfill(value: Any) -> Void { | |
if let resolved = resolved? { | |
handleResult(resolved(value: value)) | |
} else { | |
resolve(value: value) | |
} | |
} | |
func onReject(error: NSError) -> Void { | |
if let rejected = rejected? { | |
handleResult(rejected(error: error)) | |
} else { | |
reject(error: error) | |
} | |
} | |
self._handle((onFulfill: onFulfill, onReject: onReject)) | |
return | |
}) | |
} | |
/** | |
* Subscribes functions to be notified when the receiver resolves. Functions | |
* can be subscribed to either success or failure conditions. Both functions | |
* are optional. If the receiver has already resolved by the time `done` is | |
* called, the corresponding function will automatically invoke immediately. | |
* The functions will invoke on the main queue. If an error occurs, but the | |
* rejected function was not provided, an NSException will be raised. | |
*/ | |
func done(resolved: ((value: Any) -> Void)? = nil, rejected: ((error: NSError) -> Void)? = nil) -> Void { | |
func onFulfill(value: Any) -> Void { | |
resolved?(value: value) | |
} | |
func onReject(error: NSError) -> Void { | |
if let rejected = rejected? { | |
rejected(error: error) | |
} else { | |
NSException(name: NSGenericException, reason: "Error happened in Promise chain, but no handler in done: \(error)", userInfo: ["error":error]).raise() | |
} | |
} | |
self._handle((onFulfill: onFulfill, onReject: onReject)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment