Then

然后

Language: Swift 5 Platform: iOS 8+/macOS10.11 SPM compatible Build Status License: MIT Release version

原因 - 示例 - 文档 - 安装

fetchUserId().then { id in
    print("UserID : \(id)")
}.onError { e in
    print("An error occured : \(e)")
}.finally {
    print("Everything is Done :)")
}
  let userId = try! awaitPromise(fetchUserId())

因为异步代码很难编写,很难阅读,很难理解。 维护起来很痛苦

尝试一下

then 是 freshOS iOS 工具集的一部分。 在示例 App 中尝试一下! 下载启动项目

如何实现

通过使用 then 关键字,使您可以编写读起来像英语句子的异步代码
异步代码现在变得简洁灵活易于维护 ❤️

是什么

示例

之前

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
精神健全得以保存 // #告别回调地狱

文档

  1. 编写您自己的 Promise
  2. 进度
  3. 注册一个块以供稍后使用
  4. 返回一个拒绝的 promise
  5. 常用助手
  6. race
  7. recover
  8. validate
  9. retry
  10. bridgeError
  11. whenAll
  12. chain
  13. noMatterWhat
  14. unwrap
  15. AsyncTask
  16. Async/Await

编写你自己的 Promise 💪

想知道 fetchUserId() 是什么吗?
它是一个简单的函数,返回一个强类型的 promise

func fetchUserId() -> Promise<Int> {
    return Promise { resolve, reject in
        print("fetching user Id ...")
        wait { resolve(1234) }
    }
}

在这里,您通常会将虚拟等待函数替换为您的网络请求 <3

进度

至于 thenonError,您还可以调用 progress 块来处理诸如上传头像之类的事情。

uploadAvatar().progress { p in
  // Here update progressView for example
}
.then(doSomething)
.onError(showErrorPopup)
.finally(doSomething)

注册一个块以供稍后使用

我们的实现与原始 javascript Promises 略有不同。 事实上,它们并非立即启动,这是故意的。 调用 thenonErrorfinally 将自动启动它们。

调用 then 会启动一个 promise,如果它尚未启动。 在某些情况下,我们只想注册一些代码以供稍后使用。 例如,在 JSON 到 Swift 模型解析的情况下,我们经常希望将解析块附加到 JSON promises,但无需启动它们。

为了做到这一点,我们需要使用 registerThen 代替。 它与 then 完全相同,只是不立即启动 promise。

let fetchUsers:Promise<[User]> = fetchUsersJSON().registerThen(parseUsersJSON)

// Here promise is not launched yet \o/

// later...
fetchUsers.then { users in
    // YAY
}

请注意,onErrorfinally 也具有其非启动的对应项:registerOnErrorregisterFinally

返回一个拒绝的 promise

通常我们需要返回一个拒绝的 promise 如下

return Promise { _, reject in
  reject(anError)
}

可以用以下快捷方式编写

return Promise.reject(error:anError)

常用助手

Race

使用 race,您可以发送多个任务并获得第一个返回的结果

race(task1, task2, task3).then { work in
  // The first result !
}

Recover

使用 .recover,你可以为失败的 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

使用 .validate,您可以使用断言块来打破 promise 链。

您可以

例如,检查用户是否允许饮酒

fetchUserAge()
.validate { $0 > 18 }
.then { age in
  // Offer a drink
}

.validate(withError: MyError.defaultError, { $0 > 18 })`

默认情况下,失败的验证将返回 PromiseError.validationFailed

Retry

使用 retry,您可以重新启动失败的 Promise X 次。

doSomething()
  .retry(10)
  .then { v in
   // YAY!
  }.onError { e in
    // Failed 10 times in a row
  }

BridgeError

使用 .bridgeError,你可以拦截一个低级错误并返回你自己的高级错误。 典型的用例是当你收到一个 api 错误,然后你把它桥接到你自己的领域错误。

您可以

.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

使用 chain,您可以添加行为,而无需更改 Promise 链。

一个常见的用例是添加像这样的分析跟踪

extension Photo {
    public func post() -> Async<Photo> {
        return api.post(self).chain { _ in
            Tracker.trackEvent(.postPicture)
        }
    }
}

NoMatterWhat

使用 noMatterWhat,您可以添加代码以在 promise 链的中间执行,无论发生什么情况。

func fetchNext() -> Promise<[T]> {
    isLoading = true
    call.params["page"] = page + 1
    return call.fetch()
        .registerThen(parseResponse)
        .resolveOnMainThread()
        .noMatterWhat {
            self.isLoading = false
    }
}

Unwrap

使用 unwrap,您可以将 optional 转换为 promise

func fetch(userId: String?) -> Promise<Void> {
   return unwrap(userId).then {
        network.get("/user/\($0)")
    }
}

如果值为 nil,Unwrap 将使用 unwrappingFailed 错误使 promise 链失败 :)

AsyncTask

对于我们这些认为 Async 比 Promise 更清晰的人,提供了 AsyncTaskAsync<T> 类型别名。 可以根据需要将 Promise<Void> 替换为 AsyncTask,将 Promise<T> 替换为 Async<T>
这纯粹是为了视觉效果 :)

Async/Await

awaitPromise 同步等待 promise 完成并产生结果

let photos = try! awaitPromise(getPhotos())

async 接受一个块并将其包装在一个后台 Promise 中。

async {
  let photos = try awaitPromise(getPhotos())
}

请注意,我们不再需要 !,因为 async 将捕获错误。

总之,async/awaitPromise 使我们能够以同步方式编写异步代码

async {
  let userId = try awaitPromise(fetchUserId())
  let userName = try awaitPromise(fetchUserNameFromId(userId))
  let isFollowed = try awaitPromise(fetchUserFollowStatusFromName(userName))
  return isFollowed
}.then { isFollowed in
  print(isFollowed)
}.onError { e in
  // handle errors
}

Await 运算符

Await 带有 .. 简写运算符。 ..? 将回退到 nil 值而不是抛出。

let userId = try awaitPromise(fetchUserId())

可以这样写

let userId = try ..fetchUserId()

安装

Swift Package Manager (SPM) 现在是安装 Then 的官方方式。 从 5.1.3 开始,其他包管理器已被弃用,并且未来的版本将不再支持。

Swift Package Manager

Xcode > 文件 > Swift Packages > Add Package Dependency... > 粘贴 https://github.com/freshOS/Then

Cocoapods - 已弃用

target 'MyApp'
pod 'thenPromise'
use_frameworks!

Carthage - 已弃用

github "freshOS/then"

贡献者

S4cha, Max Konovalov, YannickDot, Damien, piterlouis

Swift 版本

支持者

喜欢这个项目吗? 请我们喝咖啡或每月捐款支持我们,并帮助我们继续开展活动 :)

赞助商

成为赞助商,让您的徽标显示在我们在 Github 上的 README 文件中,并链接到您的网站 :)