then
fetchUserId().then { id in
print("UserID : \(id)")
}.onError { e in
print("An error occured : \(e)")
}.finally {M
print("Everything is Done :)")
}
let userId = try! await(fetchUserId())
因为异步代码难以编写,难以阅读,难以理解。 维护起来很痛苦
试试看
then是freshOS iOS工具集的一部分。在一个示例应用中试试! 下载起始项目
如何
使用一个then
关键字,您可以通过它编写出类似英语句子的异步代码
异步代码现在更简洁
、灵活
和易于维护
什么是
- 基于流行的
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
💪
编写自己的Promise想知道fetchUserId()是什么吗?
它是一个简单的函数,返回一个强类型的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 Promise 略有不同。确实,它们不会立即启动,这是故意的。调用 then
、onError
或 finally
将自动启动它们。
调用 then
将启动承诺(如果尚未启动)。在某些情况下,我们只想为以后注册一些代码。例如,在 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提供一个回退值。
您可以
- 用值恢复
- 为特定的错误类型恢复值
- 从块中返回一个值,使您能够测试错误的类型并返回不同的值。
- 用同一类型的另一个Promise恢复
.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链
- 插入断言并返回自己的Error
例如,检查用户是否有权饮酒
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
使用 .bridgeError
,您可以拦截低级别的错误并返回自己的高级错误。经典用例是您收到一个API错误并将其桥接到自己的域错误。
您可以
- 捕获所有错误并使用自己的Error类型
- 只捕获特定错误
.bridgeError(to: MyError.defaultError)
.bridgeError(SomeError, to: MyError.defaultError)
WhenAll
使用 .whenAll
,您可以组合多个调用,并在所有Promise完成时获取所有结果。
whenAll(fetchUsersA(),fetchUsersB(), fetchUsersC()).then { allUsers in
// All the promises came back
}
链
使用 chain
,可以在不改变 Promise 链的情况下添加行为。
一个常见的用例是添加Analytics跟踪,如下所示
extension Photo {
public func post() -> Async<Photo> {
return api.post(self).chain { _ in
Tracker.trackEvent(.postPicture)
}
}
}
无所不为
使用 noMatterWhat
,你可以在 Promise 链的中间添加无论发生什么都会被执行的代码。
func fetchNext() -> Promise<[T]> {
isLoading = true
call.params["page"] = page + 1
return call.fetch()
.registerThen(parseResponse)
.resolveOnMainThread()
.noMatterWhat {
self.isLoading = false
}
}
展开
使用 unwrap
,可以将可选项转换为 Promise
func fetch(userId: String?) -> Promise<Void> {
return unwrap(userId).then {
network.get("/user/\($0)")
}
}
如果遇到 nil 值,展开会使用 unwrappingFailed
错误失败 Promise 链 :)
异步任务
AsyncTask
和 Async<T>
类型别名适用于那些认为 Async 比Promise 更清晰的人。请在实际需要的地方将 Promise<Void>
替换为 AsyncTask
,将 Promise<T>
替换为 Async<T>
。
这是纯粹为了视觉效果 :)
Async/Await
await
同步等待一个 Promise 完成,并返回结果
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
}
等待操作符
等待可以使用 ..
简写运算符。而 ..?
将回退到空值,而不是抛出错误。
let userId = try await(fetchUserId())
可以写成这样
let userId = try ..fetchUserId()
安装
Swift 包管理器
要在 Xcode 11 项目中通过 SPM 指定,请访问项目 > Swift 包。
https://github.com/freshOS/then
CocoaPods
target 'MyApp'
pod 'thenPromise'
use_frameworks!
Carthage
github "freshOS/then"
手动操作
简单地将 .swift
文件复制粘贴到您的 Xcode 项目中 :)
作为框架
获取此存储库并在示例项目中构建框架目标。然后链接到此框架。
贡献者
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.1
- Swift 5.0 -> 版本 5.0.0
- Swift 5.1 -> 版本 5.1.0
- Swift 5.1.3 -> 版本 5.1.2
赞助商
喜欢这个项目?请提供咖啡或通过每月捐赠来支持我们,帮助我们继续活动 :)
赞助商
成为赞助商,让你的标志出现在我们的 Github README 上,并提供你的网站的链接 :)