then
fetchUserId().then { id in
print("UserID : \(id)")
}.onError { e in
print("An error occured : \(e)")
}.finally {
print("Everything is Done :)")
}
let userId = try! await(fetchUserId())
因为异步代码难以编写,难以阅读,难以推理。 难以维护
尝试
Then 是 freshOS iOS 工具集的一部分。在一个示例 App 中尝试使用它!下载 Starter Project
如何
通过使用一个 then 关键字,使得您可以编写像英语句子一样易于阅读的 aSync 代码
异步代码现在更加 简洁,灵活 和 易于维护
是什么
- 基于流行的
Promise
/Future
概念 -
Async
/Await
-
progress
race
recover
validate
retry
bridgeError
chain
noMatterWhat
... - 强类型
- 纯 Swift & 轻量级
示例
之前
fetchUserId({ id in
fetchUserNameFromId(id, success: { name in
fetchUserFollowStatusFromName(name, success: { isFollowed in
// The three calls in a row succeeded YAY!
reloadList()
}, failure: { error in
// Fetching user ID failed
reloadList()
})
}, failure: { error in
// Fetching user name failed
reloadList()
})
}) { error in
// Fetching user follow status failed
reloadList()
}
🙉🙈🙊#callbackHell
之后
fetchUserId()
.then(fetchUserNameFromId)
.then(fetchUserFollowStatusFromName)
.then(updateFollowStatus)
.onError(showErrorPopup)
.finally(reloadList)
🎉 🎉 🎉
🤓
更进一步fetchUserId().then { id in
print("UserID : \(id)")
}.onError { e in
print("An error occured : \(e)")
}.finally {
print("Everything is Done :)")
}
如果我们想让这可维护,它应该像读英语句子一样
我们可以通过将我们的块提取到独立的函数中来实现
fetchUserId()
.then(printUserID)
.onError(showErrorPopup)
.finally(reloadList)
现在它很简洁,很灵活,可维护,像读英语句子一样好 <3
心理平和保存 // #goodbyeCallbackHell
文档
- 编写自己的Promise
- 进度
- 注册块以供以后使用
- 返回拒绝的Promise
- 常见助手
- race
- recover
- validate
- retry
- bridgeError
- whenAll
- chain
- noMatterWhat
- unwrap
- AsyncTask
- Async/Await
💪
编写自己的PromisefetchUserId() 是什么?
它是一个简单的函数,返回一个强类型Promise
func fetchUserId() -> Promise<Int> {
return Promise { resolve, reject in
print("fetching user Id ...")
wait { resolve(1234) }
}
}
这里通常会替换假定的等待函数,用你的网络请求替换 <3
进度
至于then
和onError
,您也可以为例如上传头像等情况调用progress
块。
uploadAvatar().progress { p in
// Here update progressView for example
}
.then(doSomething)
.onError(showErrorPopup)
.finally(doSomething)
稍后注册一个块
我们的实现与原始javascript Promises略有不同。实际上,它们不是立即启动的,这是故意的。调用then
、onError
或finally
将自动启动它们。
调用then
开始一个正在进行的promise,如果它尚未开始。在某些情况下,我们只想注册一些后续代码。例如,在JSON到Swift模型解析的情况下,我们通常希望将解析块附加到JSON承诺,但又不启动它们。
为了做到这一点,我们需要使用registerThen
。它与then
完全相同,只是不立即启动承诺。
let fetchUsers:Promise<[User]> = fetchUsersJSON().registerThen(parseUsersJSON)
// Here promise is not launched yet \o/
// later...
fetchUsers.then { users in
// YAY
}
请注意,onError
和finally
也有它们非启动的对应物:registerOnError
和registerFinally
。
返回一个拒绝的承诺
经常需要返回一个拒绝的承诺,如下所示
return Promise { _, reject in
reject(anError)
}
这可以写为以下快捷方式
return Promise.reject(error:anError)
常用的助手
竞争
使用race
,您可以发送多个任务并获得第一个返回的结果
race(task1, task2, task3).then { work in
// The first result !
}
恢复
使用.recover
,您可以为一个失败的Promise提供回滚值。
您可以
- 用值来恢复
- 为特定的错误类型指定恢复值
- 从块中返回一个值,允许您测试错误类型并返回不同的值。
- 使用相同类型的另一个Promises来恢复
.recover(with: 12)
.recover(MyError.defaultError, with: 12)
.recover { e in
if e == x { return 32 }
if e == y { return 143 }
throw MyError.defaultError
}
.recover { e -> Promise<Int> in
// Deal with the error then
return Promise<Int>.resolve(56)
// Or
return Promise<Int>.reject(e)
}
}
.recover(with: Promise<Int>.resolve(56))
注意,在块版本中,您还可以抛出自己定义的错误 \o/
验证
使用.validate
,您可以通过断言块来分裂Promise链。
您可以
- 在Promise链中插入断言
- 插入断言并返回您自己的错误
例如,检查用户是否有饮酒资格
fetchUserAge()
.validate { $0 > 18 }
.then { age in
// Offer a drink
}
.validate(withError: MyError.defaultError, { $0 > 18 })`
默认情况下,失败的验证将返回PromiseError.validationFailed
。
重试
使用retry
,您可以重启一个失败的Promise X次。
doSomething()
.retry(10)
.then { v in
// YAY!
}.onError { e in
// Failed 10 times in a row
}
桥接错误
使用.bridgeError
,您可以拦截底层错误并返回自己的高级别错误。经典的用例是当您收到API错误并将它桥接到自己的域名错误。
您可以
- 捕获所有错误并使用您自己的错误类型
- 只捕获特定错误
.bridgeError(to: MyError.defaultError)
.bridgeError(SomeError, to: MyError.defaultError)
当所有...
使用.whenAll
,您可以组合多个调用,并在所有Promise都解决后获取所有结果
whenAll(fetchUsersA(),fetchUsersB(), fetchUsersC()).then { allUsers in
// All the promises came back
}
链
使用 链
,您可以在不更改 Promise 链的情况下添加行为。
一个常见的用例是添加如下分析跟踪
extension Photo {
public func post() -> Async<Photo> {
return api.post(self).chain { _ in
Tracker.trackEvent(.postPicture)
}
}
}
不论什么
使用 不论什么
,您可以在承诺链的中间添加要执行的代码,无论发生什么。
func fetchNext() -> Promise<[T]> {
isLoading = true
call.params["page"] = page + 1
return call.fetch()
.registerThen(parseResponse)
.resolveOnMainThread()
.noMatterWhat {
self.isLoading = false
}
}
展开
使用 展开
,您可以将可选类型转换为承诺
func fetch(userId: String?) -> Promise<Void> {
return unwrap(userId).then {
network.get("/user/\($0)")
}
}
如果值为 nil,展开将使用 展开失败
错误使承诺链失败 :)
异步任务
AsyncTask
和 Async<T>
类型别名供我们使用,我们认为异步标识符比 Promise
更清晰。在需要的地方,您可以自由地将 Promise<Void>
替换为 AsyncTask
,将 Promise<T>
替换为 Async<T>
。
这只是为了视觉效果 :)
异步/等待
await
等待承诺同步完成并返回结果
let photos = try! await(getPhotos())
async
将一个块包装在后台 Promise 中。
async {
let photos = try await(getPhotos())
}
注意,我们不再需要 !
,因为 async
会捕获错误。
结合使用 async
/await
,我们可以以同步方式编写异步代码。
async {
let userId = try await(fetchUserId())
let userName = try await(fetchUserNameFromId(userId))
let isFollowed = try await(fetchUserFollowStatusFromName(userName))
return isFollowed
}.then { isFollowed in
print(isFollowed)
}.onError { e in
// handle errors
}
等待操作符
Await 操作符带有 ..
简写运算符。当使用 ..?
时,将回退到 nil 值而不是抛出异常。
let userId = try await(fetchUserId())
可以写成这样
let userId = try ..fetchUserId()
安装
CocoaPods
target 'MyApp'
pod 'thenPromise'
use_frameworks!
Carthage
github "freshOS/then"
手动安装
只需简单地将 .swift
文件复制粘贴到您的 Xcode 项目中 :)
作为一个框架
下载此存储库,并在示例项目上构建 Framework 目标。然后,链接到该框架。
贡献者
S4cha,Max Konovalov,YannickDot,Damien,piterlouis
Swift 版本
- Swift 2 -> 版本 1.4.2
- Swift 3 -> 版本 2.2.5
- Swift 4 -> 版本 3.1.0
- Swift 4.1 -> 版本 4.1.1
- Swift 4.2 -> 版本 4.2.0
- Swift 4.2.1 -> 版本 4.2.0
支持者
喜欢这个项目?提供咖啡或通过每月捐赠支持我们,帮助我们继续我们的活动:)
赞助商
成为赞助商,可在 Github 的 README 上的我们的标志下面获得链接到您的网站:)