Skip to content

Instantly share code, notes, and snippets.

@xhjkl
Last active March 23, 2019 16:01
Show Gist options
  • Save xhjkl/e0c6ca44987ae5698a2dd418ea7d4565 to your computer and use it in GitHub Desktop.
Save xhjkl/e0c6ca44987ae5698a2dd418ea7d4565 to your computer and use it in GitHub Desktop.
Promises for Swift
//
// Asynchronous computation control library
// that adds typing and chaining on top of Dispatch.
//
import class Dispatch.DispatchQueue
import struct Dispatch.DispatchQoS
/// Single unit of asynchronous computation.
///
public class Promise<T> {
internal var result: Result<T, Error>?
private weak var queue: DispatchQueue!
/// Promises are created through factory methods of `DispatchQueue`.
///
internal init(queue: DispatchQueue, result: Result<T, Error>? = nil) {
self.queue = queue
self.result = result
}
/// Schedule a promise to execute after another one.
///
/// The body only gets executed if the preceding promise
/// was fulfilled. In the other case, a new promise is returned
/// with the same reason as the broken preceding one.
///
@discardableResult
public func `then`<Y>(_ work: @escaping (T) throws -> Y) -> Promise<Y> {
return queue.promise({
switch self.result! {
case .success(let result):
return try work(result)
case .failure(let reason):
throw reason
}
})
}
/// Provide a fallback value by executing a promise.
///
/// Upon successful return, the new promise
/// becomes fulfilled with its value.
/// If the body throws, the new promise becomes broken
/// with the new reason.
///
@discardableResult
public func `else`(_ body: @escaping (Error) throws -> T) -> Promise<T> {
return queue.promise({
switch self.result! {
case .success(let result):
return result
case .failure(let reason):
return try body(reason)
}
})
}
/// Chain an action regardless of the preceding promise state.
///
@discardableResult
public func anyway<Y>(_ work: @escaping (Result<T, Error>) throws -> Y) -> Promise<Y> {
return queue.promise({
return try work(self.result!)
})
}
/// Wait until the promise finishes,
/// after that return the value or throw the reason.
///
public func join() throws -> T {
queue.joinAll()
switch self.result! {
case .success(let result):
return result
case .failure(let reason):
throw reason
}
}
}
// Factory of Promises.
//
public extension DispatchQueue {
/// Make a promise from a throwing function.
///
/// The returned value of the supplied body
/// shall become the value of the fulfilled promise.
/// If the body throws, the promise becomes broken
/// with the reason of the error thrown.
///
@discardableResult
func promise<T>(_ work: @escaping () throws -> T) -> Promise<T> {
let promise = Promise<T>(queue: self)
self.async(execute: {
// If `self` is a serial queue, keep it that way.
self.suspend()
defer { self.resume() }
do {
let result = try work()
promise.result = .success(result)
} catch (let error) {
promise.result = .failure(error)
}
})
return promise
}
/// Wrap a pair of callbacks into a promise.
///
/// The first callback is used fulfill the promise with the given value;
/// the second one is used to break it with the given reason.
///
/// After the promise has been either fulfilled or broken,
/// any subsequent calls to either of two callback have no effect.
///
/// Note that other promises shall not start
/// until this promise has either become broken or fulfilled.
///
@discardableResult
func promise<T>(
by work: @escaping (@escaping (T) -> Void, @escaping (Error) -> Void) -> ()
) -> Promise<T> {
let promise = Promise<T>(queue: self)
self.async(execute: {
self.suspend()
work({
guard promise.result == nil else { return }
promise.result = .success($0)
self.resume()
}, {
guard promise.result == nil else { return }
promise.result = .failure($0)
self.resume()
})
})
return promise
}
/// Wait until all previously submitted promises are finished.
///
func joinAll() {
self.sync(execute: {})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment