Bluebird.swift
Promise/A+ 遵循,由 Bluebird 启发,Swift 5 实现
功能
- Promise/A+ 遵循
- Swift 5
- Promise 取消
- 性能
- 轻量级
- 单元测试
- 100% 文档化
文档
https://andrewbarba.github.io/Bluebird.swift/
要求
- iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 10.2
- Swift 5
安装
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")
]
)
CocoaPods
CocoaPods 1.5.0+ 需要构建 Bluebird
pod 'Bluebird', '~> 5.0'
Carthage
github "AndrewBarba/Bluebird.swift" ~> 5.0
谁在用 Bluebird
在生产中使用 Bluebird?请通过 Pull Request 或 Issue 告诉我。
使用方法
Promise
Promise 是通用的,允许您指定它们最终将解析为的类型。创建 Promise 的首选方法是传递一个闭包,该闭包接受两个函数,一个用于调用以解析 Promise,另一个用于调用以拒绝 Promise
let promise = Promise<Int> { resolve, reject in
// - resolve(someInt)
// - reject(someError)
}
resolve 和 reject 函数可以异步或同步调用。这是一个将现有 Cocoa API 包装在自己的代码中以解决 Promise 的好方法。例如,查看一个处理图像的昂贵函数
Promise 之前
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)
}
}
}
Promise 之后
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
您可以使用 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
处理程序就会接收到该处理程序的解析,等待上一个处理程序完全解析。这对于异步控制流来说非常强大。
Grand Central Dispatch
任何接受处理程序的 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
}
tap
在不需要更改 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
使用 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
}
join
无缝连接不同类型的 Promise
join(fetchArticle(id: "123"), fetchAuthor(id: "456"))
.then { article, author in
// ...
}
map
遍历一系列元素并对每个元素执行一个操作
let articles = ...
map(articles) { article in
return favoriteService.like(article: article)
}.then { _ in
// all articles liked successfully
}.catch { error in
// handle error
}
您还可以使用 mapSeries()
按顺序遍历序列。
reduce
遍历序列并将结果汇总为一个完整解决的 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
}
all
等待所有 Promise 完成
all([
favoriteService.like(article: article1),
favoriteService.like(article: article2),
favoriteService.like(article: article3),
favoriteService.like(article: article4),
]).then { _ in
// all articles liked
}
any
使用 any
可以轻松处理竞态条件,一旦其中一个 Promise 解决,处理器就会被调用,然后再也不会被调用
let host1 = "https://east.us.com/file"
let host2 = "https://west.us.com/file"
any(download(host1), download(host2))
.then { data in
...
}
try
启动 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
}
Tests
测试在 Bitrise 上持续运行。由于 Bitrise 不支持公开测试运行,我无法链接到它们,但您可以自己运行测试,方法是打开 Xcode 项目,并从 Bluebird 方案手动运行测试。
Bluebird 与 PromiseKit 的比较
如果我说我没有说过 PromiseKit 是一个非常棒的库(它是!)那就太撒谎了,但 Bluebird 有不同的目标,这可能对不同的开发者有吸引力或没有吸引力。
Xcode 9+/Swift 4+
PromiseKit 不遗余力地保持与 Objective-C、之前版本的 Swift 和之前版本的 Xcode 的兼容性。这是一个庞大的工程,真幸运有他们在做这件事。
泛型与组合
Bluebird在整个库中都有更为复杂的泛型使用,为Swift中的Promise链组合提供了非常好的API。
Bluebird支持与任何Sequence类型一起使用map
、reduce
、all
、any
,而不仅仅是数组。例如,您可以使用Realm中的List
或Result
类型在这些函数中使用,而PromiseKit不支持这种功能。
Bluebird还支持Promise.map
和Promise.reduce
(同Bluebird.js),它们的作用与全局等效函数相同,但可以在线上对现有的Promise进行链式操作,这大大增强了Promise的组合能力。
无扩展
PromiseKit提供了许多有用的框架扩展,它们以Promise样式的函数包装核心Cocoa API。目前我没有任何计划提供此类功能,但如果这样做,它将在不同的存储库中,以便我可以保持这个存储库的简洁和严格测试。
Bluebird API 兼容性
在我重用Bluebird.js后,我开始使用PromiseKit,但在使用过程中对API的细微差异和一些实际缺失的功能感到烦恼。Bluebird.swift试图尽可能接近Bluebird.js的API。
Bluebird.js
Promise.resolve(result)
Promise.reject(error)
promise.then(handler)
promise.catch(handler)
promise.finally(() => ...)
promise.tap(value => ...)
Bluebird.swift
Promise(resolve: result)
Promise(reject: error)
promise.then(handler)
promise.catch(handler)
promise.finally { ... }
promise.tap { value in ... }
PromiseKit
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。