Last active
March 23, 2019 16:01
-
-
Save xhjkl/e0c6ca44987ae5698a2dd418ea7d4565 to your computer and use it in GitHub Desktop.
Promises for Swift
This file contains hidden or 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
// | |
// 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