Swift 的 Future 和 Promise

2020/9/13 posted in  SwiftUI

Future&Promise 来源于函数式语言,概念早在 1977 年就已经提出来了。其目的是分离一个值和产生值的方法,从而简化异步代码的处理。
Future 是一个只读的值的容器,它的值在未来某个时刻会被计算出来(产生这个值的行为是异步操作)。
Promise 是一个可写的容器,可以设置 Future 的值。

A Promise is something you make to someone else.(承诺是你对别人的承诺)
In the Future you may choose to honor (resolve) that promise, or reject it.(在未来,您可以选择兑现(解决)该承诺,或拒绝承诺。)

🌰:

常规回调

class UserLoader {
    typealias Handler = (Result<User>) -> Void
    
    func loadUser(withID id: Int, completionHandler: @escaping Handler) {
        let url = apiConfiguration.urlForLoadingUser(withID: id)

        let task = urlSession.dataTask(with: url) { [weak self] data, _, error in
            if let error = error {
                completionHandler(.error(error))
            } else {
                do {
                    let user: User = try unbox(data: data ?? Data())

                    self?.database.save(user) {
                        completionHandler(.value(user))
                    }
                } catch {
                    completionHandler(.error(error))
                }
            }
        }

        task.resume()
    }
}

采用 Futures & Promises 后,代码将变为

class UserLoader {
    func loadUser(withID id: Int) -> Future<User> {
        let url = apiConfiguration.urlForLoadingUser(withID: id)

        return urlSession.request(url: url)
                         .unboxed()
                         .saved(in: database)
    }
}

// 调用代码
let userLoader = UserLoader()
userLoader
    .loadUser(withID: userID)
    .observe { result in
    // 处理 result
}

Future

enum Result<T> {
    case value(T)
    case error(Error)
}

class Future<Value> {
    fileprivate var result: Result<Value>? {
        // Observe whenever a result is assigned, and report it
        didSet { result.map(report) }
    }
    private lazy var callbacks = [(Result<Value>) -> Void]()

    func observe(with callback: @escaping (Result<Value>) -> Void) {
        callbacks.append(callback)

        // If a result has already been set, call the callback directly
        result.map(callback)
    }

    private func report(result: Result<Value>) {
        for callback in callbacks {
            callback(result)
        }
    }
}

Promises

class Promise<Value>: Future<Value> {
    init(value: Value? = nil) {
        super.init()

        // If the value was already known at the time the promise
        // was constructed, we can report the value directly
        result = value.map(Result.value)
    }

    func resolve(with value: Value) {
        result = .value(value)
    }

    func reject(with error: Error) {
        result = .error(error)
    }
}

用法

🌰给 URLSession增加一个extension

extension URLSession {
    func request(url: URL) -> Future<Data> {
        // Start by constructing a Promise, that will later be
        // returned as a Future
        let promise = Promise<Data>()
        // Perform a data task, just like normal
        let task = dataTask(with: url) { data, _, error in
            // Reject or resolve the promise, depending on the result
            if let error = error {
                promise.reject(with: error)
            } else {
                promise.resolve(with: data ?? Data())
            }
        }
        task.resume()
        return promise
    }
}

有了这些后,我们就可以调用一个简单的网络请求

URLSession.shared.request(url: url).observe { result in
    // Handle result
}

Reference

https://juejin.im/post/6844903937032601614