借款人
借款人是一个Swift中的Promise实现。
关于Promise
Promise
代表了异步操作最终完成或失败的结果。Promise与现实生活中的承诺非常相似,一个承诺要么实现,要么失败。
异步编程:从回调到Promise
在Promise之前,回调型的API常用于异步代码。以下是一个示例
// Callback hell
func doSomethingAsync(parameters: [String: Any], completion: @escaping (Response?, Error?) -> ()) {
validate(parameters) { _, error in
if let error = error {
completion(nil, error)
} else {
self.queryDB(with: parameters) { dbResult, error in
if let error = error {
completion(nil, error)
} else if let dbResult = dbResult {
self.doServiceCall(dbResult) { response, error in
completion(response, error)
}
}
}
}
}
}
这种方式使用深度嵌套回调的特定模式通常被称为"回调地狱",因为它使代码可读性变差,且难以维护。
现在,使用Promise
// Promise
func doSomethingAsync(parameters: [String: Any]) -> Promise<Response> {
return validate(parameters)
.then {
self.queryDB(with: parameters)
}
.then { dbResult in
self.doServiceCall(dbResult)
}
}
使用方法
Promise
是一个对象,表示异步操作的最终完成或失败。
本质上,Promise就是一个返回的对象,你可以将处理器(又称回调)附加到它,而不是将处理器传递给函数。
想象有一个函数,createAudioFileAsync()
,它异步地根据配置记录生成声音文件,并给定两个处理器函数,一个在成功创建音频文件时调用,另一个在发生错误时调用。
以下是一些使用createAudioFileAsync()
的代码
func handleSuccess(url: URL) {
print("Audio file ready at URL:", url)
}
func handleFailure(error: Error) {
print("Error generating audio file:", error)
}
createAudioFileAsync(settings: audioSettings, successHandler: handleSuccess, failureHandler: handleFailure)
...使用借款人,你可以让函数返回一个Promise,你可以将处理器附加到这个Promise上
如果将 createAudioFileAsync()
重新编写为返回一个承诺(Promise),使用它可能会像这样简单
createAudioFileAsync(settings: audioSettings).then(handleSuccess, handleFailure)
这是简写形式:
let promise = createAudioFileAsync(settings: audioSettings)
promise.then(handleSuccess, handleFailure)
我们称这种为异步函数调用。这种约定有几个优点。我们将逐一探讨。
创建一个承诺(Promise)
可以使用初始化器从头开始创建一个承诺。
let promise = Promise<String> { resolve, reject in
resolve("Yay, my first promise!")
}
您还可以使用初始化器封装一个基于完成处理程序(completion handler)的 API,如 URLSession
。
let promise = Promise<Data> { resolve, reject in
session.dataTask(with: request, completionHandler: { data, response, error in
if let error = error {
reject(error)
} else if let data = data {
resolve(data)
} else {
fatalError()
}
}).resume()
}
基本上,承诺初始化器接收一个执行器(executor)函数,该函数允许我们手动解析或拒绝一个承诺。
保证
与“老式”传入处理程序(称为回调函数,callback)不同,承诺(Promise)提供了一些保证
-
使用
then()
、catch()
和finally()
添加的处理程序甚至会在异步操作完成或失败后调用。 -
通过多次调用
then()
、catch()
或finally()
可以添加多个处理程序。每个处理程序将按照它们插入的顺序依次执行。
使用承诺的其中一个优点是 链式调用(chaining)。
链式调用
需要执行两个或更多异步操作连续执行的情况非常常见,其中每个后续操作都是在前一个操作成功后开始的,下一操作将使用前一步的结果。我们通过创建 承诺链(promise chain) 来实现这一点。
这里的魔法在于,then()
函数返回一个 新的承诺(promise),与原始的不同
let promise = doSomething()
let promise2 = promise.then(handleSuccess, handleFailure)
或者
let promise2 = doSomething().then(handleSuccess, handleFailure)
或者
let promise2 = doSomething()
.then { value
handleSuccess(value)
}
.catch { error in
handleFailure(error)
}
第二个承诺(promise2
)不仅代表 doSomething()
的完成,还代表你传入的 handleSuccess
或 handleFailure
的完成,这可以是被其他返回承诺的异步函数。在这种情况下,任何添加到 promise2
的处理程序都将排在 handleSuccess
或 handleFailure
返回的承诺后面。
基本上,每个承诺都代表链中另一个异步步骤的完成。
从前,连续执行几个异步操作会导致经典的回调金字塔问题
doSomething(successHandler: { result in
doSomethingElse(result, successHandler: { newResult in
doThirdThing(newResult, successHandler: { finalResult in
print("Got the final result:", finalResult)
}, failureHandler: handleFailure)
}, failureHandler: handleFailure)
}, failureHandler: handleFailure)
使用承诺,我们将处理程序附加到返回的承诺上,从而形成一个承诺链
doSomething()
.then { result in
return doSomethingElse(result)
}
.then { newResult in
return doThirdThing(newResult)
}
.then { finalResult in
print("Got the final result:", finalResult)
}
.catch(handleFailure)
try之后串联操作
在失败之后串联操作是可能的,例如在 catch
中,这可以在动作链中失败后执行新的操作。请阅读以下示例
Promise<()> { resolve, reject in
print("Initial")
resolve(())
}
.then {
throw SomeError()
print("Do this")
}
.catch { _ in
print("Do that")
return doThatOnFailure()
}
.then {
print("Do this instead")
}
如果 doThatOnFailure()
成功完成,它会输出以下文本
Initial
Do that
Do this instead
注意:文本 "Do this" 没有显示,因为 SomeError
错误导致拒绝。
错误传播
你可能记得在"地狱金字塔"中看到过三次 handleFailure()
,而在承诺链末尾只有一次
doSomething()
.then { result in doSomethingElse(result) }
.then { newResult in doThirdThing(newResult) }
.then { finalResult in print("Got the final result:", finalResult) }
.catch(handleFailure)
基本上,如果一个承诺链中出现了异常,它会终止查看链的 catch
处理程序,这与同步代码的模型非常相似
do {
let result = try doSomethingSync()
let newResult = try doSomethingElseSync(result)
let finalResult = try doThirdThingSync(newResult)
print("Got the final result:", finalResult)
} catch {
handleFailure(error)
}
承诺通过捕获所有错误(即使抛出的是异常或编程错误)来解决"地狱金字塔"的基本缺陷,这对异步操作的功能组成至关重要。
组合
Promise.resolve()
和 Promise.reject()
是分别手动创建已解析或拒绝的承诺的快捷方式。在有些时候这很有用。
Promise.all()
和 Promise.race()
是执行并行异步操作的两个组合工具。
我们可以并行开始操作并等待它们全部完成,如下所示
Promise.all(books.map { fetchMetadata(for: $0) })
.then { allMetadata in
zip(books, allMetadata).forEach { book, metadata in
print("\(book.title) metadata: \(metadata)")
}
}
安装
Promisor 通过 CocoaPods 可用。要安装它,只需将以下行添加到您的 Podfile
pod 'Promisor'
作者
Ennio Bovyn, [email protected]
许可协议
Promisor可在MIT许可下使用。更多信息请参阅LICENSE文件。