Refactoring your code

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 of the benefits of Swift being a static, strongly-typed language is its great support for refactoring. When you refactor some code, the compiler will flag any mistake you might have made even before attempting to run your refactored code. This does not mean, though, that you do not need tests for successful refactoring. On the contrary, it is a good practice to refactor your code only once you have a decent unit-test suite in place. We will cover unit testing specifically in Chapter 14, Testing Your Code with Unit and UI Tests.

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.