[译] 探究 Swift 中的 Futures Promises
- 原文地址:Under the hood of Futures & Promises in Swift
- 原文作者:John Sundell
- 譯文出自:掘金翻譯計劃
- 本文永久鏈接:github.com/xitu/gold-m…
- 譯者:oOatuo
- 校對者:Kangkang,?Richard_Lee
異步編程可以說是構(gòu)建大多數(shù)應(yīng)用程序最困難的部分之一。無論是處理后臺任務(wù),例如網(wǎng)絡(luò)請求,在多個線程中并行執(zhí)行重操作,還是延遲執(zhí)行代碼,這些任務(wù)往往會中斷,并使我們很難調(diào)試問題。
正因為如此,許多解決方案都是為了解決上述問題而發(fā)明的 - 主要是圍繞異步編程創(chuàng)建抽象,使其更易于理解和推理。對于大多數(shù)的解決方案來說,它們都是在"回調(diào)地獄"中提供幫助的,也就是當你有多個嵌套的閉包為了處理同一個異步操作的不同部分的時候。
這周,讓我們來看一個這樣的解決方案 -?Futures & Promises?- 讓我們打開"引擎蓋",看看它們是如何工作的。。
A promise about the future
當介紹 Futures & Promises 的概念時,大多數(shù)人首先會問的是?Future 和 Promise 有什么區(qū)別?。在我看來,最簡單易懂的理解是這樣的:
- Promise?是你對別人所作的承諾。
- 在?Future?中,你可能會選擇兌現(xiàn)(解決)這個 promise,或者拒絕它。
如果我們使用上面的定義,Futures & Promises 變成了一枚硬幣的正反面。一個 Promise 被構(gòu)造,然后返回一個 Future,在那里它可以被用來在稍后提取信息。
那么這些在代碼中看起來是怎樣的?
讓我們來看一個異步的操作,這里我們從網(wǎng)絡(luò)加載一個 "User" 的數(shù)據(jù),將其轉(zhuǎn)換成模型,最后將它保存到一個本地數(shù)據(jù)庫中。用”老式的辦法“,閉包,它看起來是這樣的:
class UserLoader {typealias Handler = (Result<User>) -> Voidfunc loadUser(withID id: Int, completionHandler: @escaping Handler) {let url = apiConfiguration.urlForLoadingUser(withID: id)let task = urlSession.dataTask(with: url) { [weak self] data, _, error inif 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()} }正如我們可以看到的,即使有一個非常簡單(非常常見)的操作,我們最終得到了相當深的嵌套代碼。這是用 Future & Promise 替換之后的樣子:
class UserLoader {func loadUser(withID id: Int) -> Future<User> {let url = apiConfiguration.urlForLoadingUser(withID: id)return urlSession.request(url: url).unboxed().saved(in: database)} }這是調(diào)用時的寫法:
let userLoader = UserLoader() userLoader.loadUser(withID: userID).observe { result in// Handle result }現(xiàn)在上面的代碼可能看起來有一點黑魔法(所有其他的代碼去哪了?!),所以讓我們來深入研究一下它是如何實現(xiàn)的。
探究 future
就像編程中的大多數(shù)事情一樣,有許多不同的方式來實現(xiàn) Futures & Promises。在本文中,我將提供一個簡單的實現(xiàn),最后將會有一些流行框架的鏈接,這些框架提供了更多的功能。
讓我們開始探究下?Future?的實現(xiàn),這是從異步操作中公開返回的。它提供了一種只讀的方式來觀察每當被賦值的時候以及維護一個觀察回調(diào)列表,像這樣:
class Future<Value> {fileprivate var result: Result<Value>? {// Observe whenever a result is assigned, and report itdidSet { 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 directlyresult.map(callback)}private func report(result: Result<Value>) {for callback in callbacks {callback(result)}} }生成 promise
接下來,硬幣的反面,Promise?是?Future?的子類,用來添加解決*和拒絕*它的 API。解決一個承諾的結(jié)果是,在未來成功地完成并返回一個值,而拒絕它會導(dǎo)致一個錯誤。像這樣:
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 directlyresult = value.map(Result.value)}func resolve(with value: Value) {result = .value(value)}func reject(with error: Error) {result = .error(error)} }正如你看到的,Futures & Promises 的基本實現(xiàn)非常簡單。我們從使用這些方法中獲得的很多神奇之處在于,這些擴展可以增加連鎖和改變未來的方式,使我們能夠構(gòu)建這些漂亮的操作鏈,就像我們在 UserLoader 中所做的那樣。
但是,如果不添加用于鏈式操作的api,我們就可以構(gòu)造用戶加載異步鏈的第一部分 -urlSession.request(url:)。在異步抽象中,一個常見的做法是在 SDK 和 Swift 標準庫之上提供方便的 API,所以我們也會在這里做這些。request(url:)?方法將是?URLSession?的一個擴展,讓它可以用作基于 Future/Promise 的 API。
extension URLSession {func request(url: URL) -> Future<Data> {// Start by constructing a Promise, that will later be// returned as a Futurelet promise = Promise<Data>()// Perform a data task, just like normallet task = dataTask(with: url) { data, _, error in// Reject or resolve the promise, depending on the resultif let error = error {promise.reject(with: error)} else {promise.resolve(with: data ?? Data())}}task.resume()return promise} }我們現(xiàn)在可以通過簡單地執(zhí)行以下操作來執(zhí)行網(wǎng)絡(luò)請求:
URLSession.shared.request(url: url).observe { result in// Handle result }鏈式
接下來,讓我們看一下如何將多個 future 組合在一起,形成一條鏈 — 例如當我們加載數(shù)據(jù)時,將其解包并在 UserLoader 中將實例保存到數(shù)據(jù)庫中。
鏈式的寫法涉及到提供一個閉包,該閉包可以返回一個新值的 future。這將使我們能夠從一個操作獲得結(jié)果,將其傳遞給下一個操作,并從該操作返回一個新值。讓我們來看一看:
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 methodlet promise = Promise<NextValue>()// Observe the current futureobserve { result inswitch result {case .value(let value):do {// Attempt to construct a new future given// the value from the first onelet future = try closure(value)// Observe the "nested" future, and once it// completes, resolve/reject the "wrapper" futurefuture.observe { result inswitch 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} }使用上面的方法,我們現(xiàn)在可以給?Savable?類型的 future?添加一個擴展,來確保數(shù)據(jù)一旦可用時,能夠輕松地保存到數(shù)據(jù)庫。
extension Future where Value: Savable {func saved(in database: Database) -> Future<Value> {return chained { user inlet promise = Promise<Value>()database.save(user) {promise.resolve(with: user)}return promise}} }現(xiàn)在我們來挖掘下 Futures & Promises 的真正潛力,我們可以看到 API 變得多么容易擴展,因為我們可以在?Future?的類中使用不同的通用約束,方便地為不同的值和操作添加方便的 API。
轉(zhuǎn)換
雖然鏈式調(diào)用提供了一個強大的方式來有序地執(zhí)行異步操作,但有時你只是想要對值進行簡單的同步轉(zhuǎn)換 - 為此,我們將添加對轉(zhuǎn)換的支持。
轉(zhuǎn)換直接完成,可以隨意地拋出,對于 JSON 解析或?qū)⒁环N類型的值轉(zhuǎn)換為另一種類型來說是完美的。就像?chained()?那樣,我們將添加一個?transformed()?方法作為?Future?的擴展,像這樣:
extension Future {func transformed<NextValue>(with closure: @escaping (Value) throws -> NextValue) -> Future<NextValue> {return chained { value inreturn try Promise(value: closure(value))}} }正如你在上面看到的,轉(zhuǎn)換實際上是一個鏈式操作的同步版本,因為它的值是直接已知的 - 它構(gòu)建時只是將它傳遞給一個新?Promise?。
使用我們新的變換 API, 我們現(xiàn)在可以添加支持,將?Data?類型 的 future 轉(zhuǎn)變?yōu)橐粋€Unboxable?類型(JSON可解碼) 的 future類型,像這樣:
extension Future where Value == Data {func unboxed<NextValue: Unboxable>() -> Future<NextValue> {return transformed { try unbox(data: $0) }} }整合所有
現(xiàn)在,我們有了把?UserLoader?升級到支持 Futures & Promises 的所有部分。我將把操作分解為每一行,這樣就更容易看到每一步發(fā)生了什么:
class UserLoader {func loadUser(withID id: Int) -> Future<User> {let url = apiConfiguration.urlForLoadingUser(withID: id)// Request the URL, returning datalet requestFuture = urlSession.request(url: url)// Transform the loaded data into a userlet unboxedFuture: Future<User> = requestFuture.unboxed()// Save the user in the databaselet savedFuture = unboxedFuture.saved(in: database)// Return the last future, as it marks the end of the chainreturn savedFuture} }當然,我們也可以做我們剛開始做的事情,把所有的調(diào)用串在一起 (這也給我們帶來了利用 Swift 的類型推斷來推斷?User?類型的 future 的好處):
class UserLoader {func loadUser(withID id: Int) -> Future<User> {let url = apiConfiguration.urlForLoadingUser(withID: id)return urlSession.request(url: url).unboxed().saved(in: database)} }結(jié)論
在編寫異步代碼時,Futures & Promises 是一個非常強大的工具,特別是當您需要將多個操作和轉(zhuǎn)換組合在一起時。它幾乎使您能夠像同步那樣去編寫異步代碼,這可以提高可讀性,并使在需要時可以更容易地移動。
然而,就像大多數(shù)抽象化一樣,你本質(zhì)上是在掩蓋復(fù)雜性,把大部分的重舉移到幕后。因此,盡管?urlSession.request(url:)?從外部看,API看起來很好,但調(diào)試和理解到底發(fā)生了什么都會變得更加困難。
我的建議是,如果你在使用 Futures & Promises,那就是讓你的調(diào)用鏈盡可能精簡。記住,好的文檔和可靠的單元測試可以幫助你避免很多麻煩和棘手的調(diào)試。
以下是一些流行的 Swift 版本的 Futures & Promises 開源框架:
- PromiseKit
- BrightFutures
- When
- Then
你也可以在?GitHub?上找到該篇文章涉及的的所有代碼。
如果有問題,歡迎留言。我非常希望聽到你的建議!你可以在下面留言,或者在 Twitter@johnsundell?聯(lián)系我。
另外,你可以獲取最新的?Sundell 的 Swift 播客,我和來自社區(qū)的游客都會在上面回答你關(guān)于 Swift 開發(fā)的問題。
感謝閱讀 。
原文發(fā)布時間為:2017年9月6日
本文來自云棲社區(qū)合作伙伴掘金,了解相關(guān)信息可以關(guān)注掘金網(wǎng)站。
總結(jié)
以上是生活随笔為你收集整理的[译] 探究 Swift 中的 Futures Promises的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: security NO.2
- 下一篇: 阿里云高级技术专家空见: CDN的数据化