As you know, in Chapter 12, Futures, Promises, and Reactive Programming, we created a streamlined implementation of futures and promises packaged as an Xcode playground. That code is certainly a good candidate for extracting a framework that we can reuse in other projects. The playground mixed the futures and promises implementation with the code using it. So our first refactoring step is keeping just the futures and promises implementation and removing the rest, which is highlighted in italics in the following snippet (to keep the listing short, we have left only the signatures of the functions to be removed):
public enum Result<Value> {
case success(Value)
case failure(Error)
}
enum SimpleError: Error { ... }
typealias Callback<T> = (Result<T>) -> Void
public class Future<T> {
internal var result : Result<T>? {
didSet {
if let result = result, let callback = callback {
callback(result)
}
}
}
var callback : Callback<T>?
init(_ callback: Callback<T>? = nil) {
self.callback = callback
}
func then(_ callback: @escaping Callback<T>) {
self.callback = callback
if let result = result {
callback(result)
}
}
}
public final class Promise<T> : Future<T> {
func resolve(_ value: T) {
result = .success(value)
}
func reject(_ error: Error) {
result = .failure(error)
}
}
import Dispatch
func asyncOperation(_ delay: Double) -> Promise<String> {
...
}
let future : Future<String> = asyncOperation(1.0)
future.then { result in
...
}
func asyncOperation2() -> Promise<String> {
...
}
func asyncOperation3(_ str : String) -> Promise<Int> {
...
}
func asyncOperation4(_ input : Int) -> Promise<Double> {
...
}
extension Future {
func chain<Z>(_ cbk: @escaping (T) -> Future<Z>) -> Future<Z> {
let p = Promise<Z>()
self.then { result in
switch result {
case .success(let value): cbk(value).then { r in p.result = r }
case .failure(let error): p.result = .failure(error)
}
}
return p
}
}
let promise2 = asyncOperation2()
promise2.chain { result in
...
}
As you can see, we have a couple of types, ResultValue<T> and Callback<T>; two classes, Future<T> and Promise<T>; and an extension to the Future<T> class. Admittedly, the amount of code for all that is not much, but we want to separate the three classes across different files to better support their evolution. Additionally, it seems a good idea to have all of our shared types in their own modules. So we may want to create the following four files using our preferred text editor: Types.swift, Future.swift, Promise.swift, and Future+Chain.swift. One thing we should be really careful about is specifying the correct protection level for all types, classes, and methods. In fact, it is not enough to mark as public all the classes you want as part of your framework API, since according to Swift default protection rules, public classes default to having internal protection. This works great when you are writing your code for a monolithic app, but is not necessarily what you want when writing libraries, since, if you do not declare a method as public, it will not be accessible from the outside. Keeping this in mind, here is the content of the four files in our package:
// FuturesAndPromises
// Types
public enum Result<Value> {
case success(Value)
case failure(Error)
}
public typealias Callback<T> = (Result<T>) -> Void
// FuturesAndPromises
// Future
public class Future<T> {
internal var result : Result<T>? {
didSet {
if let result = result, let callback = callback {
callback(result)
}
}
}
var callback : Callback<T>?
public init(_ callback: Callback<T>? = nil) {
self.callback = callback
}
public func then(_ callback: @escaping Callback<T>) {
self.callback = callback
if let result = result {
callback(result)
}
}
}
// FuturesAndPromises
// Promise
public final class Promise<T> : Future<T> {
public func resolve(_ value: T) {
result = .success(value)
}
public func reject(_ error: Error) {
result = .failure(error)
}
}
// FuturesAndPromises
// Future+Chain
extension Future {
public func chain<Z>(_ cbk: @escaping (T) -> Future<Z>) -> Future<Z> {
let p = Promise<Z>()
self.then { result in
switch result {
case .success(let value): cbk(value).then { r in p.result = r }
case .failure(let error): p.result = .failure(error)
}
}
return p
}
}
One additional refactoring that could help to make our code more easily reusable is replacing our actual Result definition – which is tied to using Error to represent an error condition – with a new definition that is on the error type. This task is left as an exercise to the reader. As a hint, you could consider the following definition for your new Result type:
public enum Result<Value, Err> {
case success(Value)
case failure(Err)
}
One further thing we should never forget when creating a framework is providing an extensive unit-testing suite. We already looked into how you can define unit tests using SPM in the Adding features to the library section, so the task of extracting a test from our initial playground is also left as an exercise to the reader.