符合 Promise/A+ 规范,灵感来源于 Bluebird,使用 Swift 5 实现
https://andrewbarba.github.io/Bluebird.swift/
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "My App",
dependencies: [
.package(url: "https://github.com/AndrewBarba/Bluebird.swift.git", from: "5.1.0")
]
)
构建 Bluebird 需要 CocoaPods 1.5.0+
pod 'Bluebird', '~> 5.0'
github "AndrewBarba/Bluebird.swift" ~> 5.0
在生产环境中使用 Bluebird?请通过 Pull Request 或 Issue 告知我。
Promises 是泛型的,允许您指定它们最终将解析成的类型。 创建 Promise 的首选方法是传入一个闭包,该闭包接受两个函数,一个用于解析 Promise,另一个用于拒绝 Promise
let promise = Promise<Int> { resolve, reject in
// - resolve(someInt)
// - reject(someError)
}
resolve
和 reject
函数可以异步或同步调用。 这是一种很棒的方式来包装现有的 Cocoa API,以便在您自己的代码中解析 Promises。 例如,看一个操作图像的昂贵的函数
func performExpensiveOperation(onImage image: UIImage, completion: @escaping (UIImage?, Error?) -> Void) {
DispatchQueue(label: "image.operation").async {
do {
let image = try ...
completion(image, nil)
} catch {
completion(nil, error)
}
}
}
func performExpensiveOperation(onImage image: UIImage) -> Promise<UIImage> {
return Promise<UIImage> { resolve, reject in
DispatchQueue(label: "image.operation").async {
do {
let image = try ...
resolve(image)
} catch {
reject(error)
}
}
}
}
好吧,所以函数内部的主体看起来几乎相同... 但是看看函数签名看起来好多了!
没有更多的完成处理程序,没有更多的可选图像,没有更多的可选错误。 原始函数中的可选参数是一个明显的标志,表明您将在不久的将来进行保护和解包。 通过 Promise 实现,该逻辑被良好的设计所隐藏。 现在使用这个新函数是一种乐趣
let original: UIImage = ...
performExpensiveOperation(onImage: original)
.then { newImage in
// do something with the new image
}
.catch { error in
// something went wrong, handle the error
}
您可以使用 then
方法轻松地执行一系列操作
authService.login(email: email, password: password)
.then { auth in userService.read(with: auth) }
.then { user in favoriteService.list(for: user) }
.then { favorites in ... }
请注意,每次您从 then
处理程序返回 Promise(或值)时,下一个 then
处理程序都会收到该处理程序的解析结果,并等待前一个处理程序完全解析。 这对于异步控制流非常强大。
Bluebird
中接受处理程序的任何方法也接受 DispatchQueue
,因此您可以控制希望处理程序在哪个队列上运行
userService.read(id: "123")
.then(on: backgroundQueue) { user -> UIImage in
let image = UIImage(user: user)
... perform complex image operation ...
return image
}
.then(on: .main) { image in
self.imageView.image = image
}
默认情况下,所有处理程序都在 .main
队列上运行。
使用 catch
来处理/从 Promise 链中发生的错误中恢复
authService.login(email: email, password: password)
.then { auth in userService.read(with: auth) }
.then { user in favoriteService.list(for: user) }
.then { favorites in ... }
.catch { error in
self.present(error: error)
}
上面,如果任何 then
处理程序 throw
抛出一个错误,或者如果从处理程序返回的 Promise 之一拒绝,则将调用最终的 catch 处理程序。
您还可以在运行多个异步操作时执行复杂的恢复
Bluebird.try { performFirstOp().catch(handleOpError) }
.then { performSecondOp().catch(handleOpError) }
.then { performThirdOp().catch(handleOpError) }
.then { performFourthOp().catch(handleOpError) }
.then {
// all completed
}
适用于在 Promise 链中间执行操作而不更改 Promise 的类型
authService.login(email: email, password: password)
.tap { auth in print(auth) }
.then { auth in userService.read(with: auth) }
.tap { user in print(user) }
.then { user in favoriteService.list(for: user) }
.then { favorites in ... }
您还可以从 tap
处理程序返回 Promise,并且链将等待该 Promise 解析
authService.login(email: email, password: password)
.then { auth in userService.read(with: auth) }
.tap { user in userService.updateLastActive(for: user) }
.then { user in favoriteService.list(for: user) }
.then { favorites in ... }
使用 finally
,您可以注册一个处理程序以在 Promise 链的末尾运行,无论其结果如何
spinner.startAnimating()
authService.login(email: email, password: "bad password")
.then { auth in userService.read(with: auth) } // will not run
.then { user in favoriteService.list(for: user) } // will not run
.finally { // this will run!
spinner.stopAnimating()
}
.catch { error in
// handle error
}
无缝地连接不同类型的 Promises
join(fetchArticle(id: "123"), fetchAuthor(id: "456"))
.then { article, author in
// ...
}
迭代一个元素序列并执行每个操作
let articles = ...
map(articles) { article in
return favoriteService.like(article: article)
}.then { _ in
// all articles liked successfully
}.catch { error in
// handle error
}
您还可以使用 mapSeries()
串行迭代序列。
迭代一个序列并减少到解析为单个值的 Promise
let users = ...
reduce(users, 0) { partialTime, user in
return userService.getActiveTime(for: user).then { time in
return partialTime + time
}
}.then { totalTime in
// calculated total time spent in app
}.catch { error in
// handle error
}
等待所有 promise 完成
all([
favoriteService.like(article: article1),
favoriteService.like(article: article2),
favoriteService.like(article: article3),
favoriteService.like(article: article4),
]).then { _ in
// all articles liked
}
使用 any
轻松处理竞争条件,一旦一个 Promise 解析,处理程序就会被调用,并且永远不会再次被调用
let host1 = "https://east.us.com/file"
let host2 = "https://west.us.com/file"
any(download(host1), download(host2))
.then { data in
...
}
启动 Promise 链
// Prefix with Bluebird since try is reserved in Swift
Bluebird.try {
authService.login(email: email, password: password)
}.then { auth in
// handle login
}.catch { error in
// handle error
}
测试在 Bitrise 上持续运行。 由于 Bitrise 不支持公共测试运行,我无法链接到它们,但您可以通过打开 Xcode 项目并从 Bluebird scheme 手动运行测试来自己运行测试。
如果我说 PromiseKit 不是一个很棒的库,那我就是在撒谎(它确实很棒!),但 Bluebird 有不同的目标,这些目标可能对不同的开发人员有吸引力,也可能没有吸引力。
PromiseKit 花费大量精力来维护与 Objective-C、以前版本的 Swift 和以前版本的 Xcode 的兼容性。 那是一堆工作,上帝保佑他们。
Bluebird 在整个库中更复杂地使用了泛型,为我们提供了非常好的 API,用于在 Swift 中组合 Promise 链。
Bluebird 支持 map
、reduce
、all
、any
,支持任何 Sequence 类型,而不仅仅是数组。 例如,您可以在所有这些函数中使用 Realm 的 List
或 Result
类型,您不能使用 PromiseKit 执行此操作。
Bluebird 还支持 Promise.map
和 Promise.reduce
(与 Bluebird.js 相同),它们的行为与它们的全局等效项相同,但可以在现有 Promise 上以内联方式链接,从而大大增强了 Promise 组合。
PromiseKit 提供了许多有用的框架扩展,这些扩展将核心 Cocoa API 包装在 Promise 风格的函数中。 我目前没有计划提供这样的功能,但如果我这样做,它将在不同的存储库中,以便我可以保持这个存储库精简且经过良好测试。
我在 Node/JavaScript 项目中大量使用 Bluebird.js 之后开始使用 PromiseKit,但对细微的 API 差异以及完全缺少的一些东西感到恼火。 Bluebird.swift 尝试紧密遵循 Bluebird.js 的 API
Promise.resolve(result)
Promise.reject(error)
promise.then(handler)
promise.catch(handler)
promise.finally(() => ...)
promise.tap(value => ...)
Promise(resolve: result)
Promise(reject: error)
promise.then(handler)
promise.catch(handler)
promise.finally { ... }
promise.tap { value in ... }
Promise(value: result)
Promise(error: error)
promise.then(execute: handler)
promise.catch(execute: handler)
promise.always { ... }
promise.tap { result in
switch result {
case .fullfilled(let value):
...
case .rejected(let error):
...
}
}
这些只是一些差异,并且 Bluebird.swift 肯定缺少 Bluebird.js 中的功能,但我的目标是缩小差距并继续维护一个 API,使其在适用的情况下更紧密地匹配。