Future & Promise 实现分析(Swift)

同步与异步

一般咱们将消息通讯分红同步和异步两种,其中同步就是消息的发送方要等待消息返回才能继续处理其它事情,而异步就是消息的发送方不须要等待消息返回就能够处理其它事情。异步容许咱们同时作更多事情,同时得到更高的性能。异步也是比较灵活复杂的,不是本文重点不过多阐述。git

代码块与闭包

在 GCD (Grand Central Dispatcher) 当中,若是咱们想要在非主线程当中执行某些操做的话,那么咱们就须要闭包的帮忙。github

func asyncFunction(callback:(Result) -> Void) -> Void {
	dispatch_async(queue) {
		let x = Result("Hello!")
		callback(x)
	}
}
复制代码

封装了一个 asyncFunction 方法,从中我建立了某种 Result,可是若是我想要将这个 Result 回调的话,那么我就必需要提供一个回调。相似代码相信你必定写过。swift

回调闭包的一些问题

回调地狱

当一项任务须要分红多个异步阶段完成时,就须要在每一个阶段的回调函数中加入下阶段回调的代码,最终产生下面这样金字塔形状的代码:api

func processImageData1(completionBlock: (result: Image) -> Void) { 
    loadWebResource("dataprofile.txt") { dataResource in 
        loadWebResource("imagedata.dat") { imageResource in 
            decodeImage(dataResource, imageResource) { imageTmp in 
                dewarpAndCleanupImage(imageTmp) { imageResult in 
                    completionBlock(imageResult)
                 }
             }
         }
     }
}

processImageData1 { image in display(image)} 
复制代码

这个嵌套的回调使得问题追踪变得很困难。能够想象当回调层次继续增长时,代码有多恐怖。Futures/Promises 能够帮咱们摆脱回调的困扰。promise

Future 和 Promise

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.(在将来,您能够选择兑现(解决)该承诺,或拒绝承诺。)app

咱们能够看下面这个例子,体会下他们的威力:异步

采用常规的回调写法

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
}
复制代码

能够很容易发现采用 Futures & Promises 后,代码量瞬间减小一大半。这里面到底有啥猫腻?

Future

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)
        }
    }
}
复制代码

Promise

PromiseFuture 的一个子类,它提供了用于兑现和拒绝的承诺方法。 兑现承诺结果将给 Future 设置一个完成的值,拒绝的承诺将给 Future 设置一个 error。

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
}
复制代码

Chaining

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

复制代码

网络请求封装完成,可是 unboxedsaved 又是如何实现?

在实现这些方法以前,咱们须要实现一个基础性的方法,chained:

extension Future {
    func chained<NextValue>(with closure: @escaping (Value) throws -> Future<NextValue>) -> Future<NextValue> {
        // Start by constructing a "wrapper" promise that will be
        // returned from this method
        let promise = Promise<NextValue>()

        // Observe the current future
        observe { result in
            switch result {
            case .value(let value):
                do {
                    // Attempt to construct a new future given
                    // the value from the first one
                    let future = try closure(value)

                    // Observe the "nested" future, and once it
                    // completes, resolve/reject the "wrapper" future
                    future.observe { result in
                        switch result {
                        case .value(let value):
                            promise.resolve(with: value)
                        case .error(let error):
                            promise.reject(with: error)
                        }
                    }
                } catch {
                    promise.reject(with: error)
                }
            case .error(let error):
                promise.reject(with: error)
            }
        }

        return promise
    }
}
复制代码

基于 chained 咱们能够容易实现 saved:

extension Future where Value: Savable {
    func saved(in database: Database) -> Future<Value> {
        return chained { user in
            let promise = Promise<Value>()

            database.save(user) {
                promise.resolve(with: user)
            }

            return promise
        }
    }
}
复制代码

Transforms

虽然 chaining 提供了顺序执行异步操做的方式,但有些时候咱们只想进行简单的同步转换获取里面的值。为此咱们在给 Future 添加个 transformed 方法:

extension Future {
    func transformed<NextValue>(with closure: @escaping (Value) throws -> NextValue) -> Future<NextValue> {
        return chained { value in
            return try Promise(value: closure(value))
        }
    }
}
复制代码

转换是连接操做的同步版本,在构造新的 Promise 时候,对值进行转换转换。这种操做有个很是适合 JSON 解析或将一种类型的值转换为另外一种类型。

咱们能够将一个值为 Data 类型的Future 转换为值为 Unboxable 类型的 Future:

extension Future where Value == Data {
    func unboxed<NextValue: Unboxable>() -> Future<NextValue> {
        return transformed { try unbox(data: $0) }
    }
}
复制代码

整合

经过上面 Future & Promises 的实现,UserLoader 能够很愉悦的实现

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

        // Request the URL, returning data
        let requestFuture = urlSession.request(url: url)

        // Transform the loaded data into a user
        let unboxedFuture: Future<User> = requestFuture.unboxed()

        // Save the user in the database
        let savedFuture = unboxedFuture.saved(in: database)

        // Return the last future, as it marks the end of the chain
        return savedFuture
    }
}
复制代码

上面的方式调用有没有发现感受是在调用同步代码同样。

经过链式调用,代码将会是咱们一开始提到的那种方式处理:

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

        return urlSession.request(url: url)
                         .unboxed()
                         .saved(in: database)
    }
}
复制代码

上面是比较简化的 Future&Promise。在 github 中有不少开源库能够参考:

SwiftNIO

为何本文会提到 Future/Promise 这个概念,重点仍是想说下 SwiftNIO。在 SwiftNIO 中也融入了这个概念。在 Vapor3 的时候咱们会大量看到 Future 这样的返回,而后也会有不少链式方法的调用。

  • flatMap
  • map
  • transform
  • flatten
  • do/catch
  • catchMap/catchFlatMap
  • always
  • wait
  • request.future(_:)

若是一开始没有接触这个概念,理解上可能会有必定的难度。

那在 SwiftNIO 是如何实现这个异步机制的?

源码在此

下期咱们将进行讲解!

总结

咱们从一个最初的 UserLoader 的闭包回调到 Future&Promise 的链式调用,让咱们能够知道能够这种模式去处理异步。在实现上也不难理解,核心点 Future 表示一个将来的值,Promise 用来设置 Future 的值,Chaining(对现有的 Future 进行操做,获取到当前 Future 的值经过 Promise 进行处理,返回一个新的 Future。) 是个利器,可细细体会一番。

参考阅读

更多阅读,可订阅官方微信公众号:

相关文章
相关标签/搜索