Skip to content

Instantly share code, notes, and snippets.

@DonaldHays
Created June 9, 2014 00:56
Show Gist options
  • Save DonaldHays/f20fdff9b410e378b164 to your computer and use it in GitHub Desktop.
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
//
// 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