Ergo

Ergo 是一个基于 Promise 管道的并发编程框架。 它可以帮助您避免复杂异步任务中的回调地狱。

codebeat badge build test SwiftPM Compatible Version License Platform

示例

要运行示例项目,请克隆仓库,并首先从 Example 目录运行 pod install

要求

仅 Swift Package Manager

安装

Cocoapods

Ergo 可以通过 CocoaPods 获得。 要安装它,只需将以下行添加到您的 Podfile 中

pod 'Ergo', '~> 1.4.0'

通过 XCode 使用 Swift Package Manager

通过 Package.swift 使用 Swift Package Manager

Package.swift 中将其添加为您的目标依赖项

dependencies: [
  .package(url: "https://github.com/hainayanda/Ergo.git", .upToNextMajor(from: "1.4.0"))
]

在您的目标中使用它,名称为 Ergo

 .target(
  name: "MyModule",
  dependencies: ["Ergo"]
)

作者

Nayanda Haberty, hainayanda@outlook.com

许可证

Impose 在 MIT 许可证下可用。 有关更多信息,请参阅 LICENSE 文件。

基本用法

Ergo 利用了 Thenable 协议,该协议在 Promise 类中实现,该类充当并发任务的代理。 要在 Promise 中创建并发任务,只需使用您要运行的任何任务调用全局方法 runPromise

runPromise {
  print("I'm running in the DispatchQueue.global(qos: .background)")
}

您可以传递 DispatchQueue 以在这些队列上运行。

runPromise(on: .main) {
  print("I'm running in the DispatchQueue.main")
}

链接 Promise

Promise 设计为可以与其他 Promise 链接。 要链接它,只需在每个 Promise 之后调用 then

runPromise {
  print("I'm running in the DispatchQueue.global(qos: .background)")
}.then {
  print("I'm running on the same DispatchQueue as previous")
}.then(on: .main) {
  print("I'm running in the DispatchQueue.main")
}

您可以根据需要链接任意多次。 所有链接的 Promise 在完成其任务后将从链中释放。

您还可以将值从一个 Promise 传递到另一个 Promise,以便在那里使用它。

runPromise {
  return "from first promise"
}.then { fromPrevious -> String in
  print(fromPrevious)
  return "from second promise"
}.then(on: .main) { fromPrevious in
  print(fromPrevious)
}

多个 Thenable

Promise 可以由多个 Thenable 处理。 您需要做的就是在特定的 Promise 之后根据需要调用尽可能多的 Thenable

let myPromise = runPromise {
  return Bool.random()
}

myPromise.then { result in
  guard result else { return }
  print("this run when true")
}

myPromise.then { result in
  guard !result else { return }
  print("this run when false")
}

处理错误

默认情况下,Promise 闭包是可抛出的。 您始终可以在 Promise 闭包中抛出错误,以停止执行下一个 Thenable

runPromise {
  print("no error here")
}.then {
  throw MyError()
}.then(on: .main) { 
  print("this line will not be executed because previous closure throw an error")
}

您可以在 then 之后添加错误处理程序闭包,以捕获错误并对其执行某些操作。

runPromise {
  print("no error here")
}.then {
  throw MyError()
}.handle {
  print($0)
  print("this line will executed with error throwed")
}.then(on: .main) { 
  print("this line will not be executed because previous closure throw an error")
}.handle {
  print($0)
  print("this line will executed with previous error throwed")
}

Promise 抛出的错误将始终传递到其所有子 Promise 中。

Finally 块

Promise 具有 finally 块,无论之前的 Promise 是否出错,它始终会被执行。 它将产生另一个 promise,该 promise 将在 finally 执行后被调用。

runPromise {
  print("no error here")
}.then {
  throw MyError()
}.then(on: .main) { 
  print("this line will not be executed because previous closure throw an error")
}.finally { result, error in
  print("this line be executed. Result will be nil and error will be MyError")
}.then {
  print("this line will be executed after finally block finished")
}.finally { result, error in
  print("this line always be executed after all promise is done")
}

超时

您可以为您的 promise 添加超时,如果在给定的超时后任务未完成,它将自动抛出错误。

runPromise(timeout: 1) {
  doLongTask()
}.then {
  print("task is run for less than 1 second")
}.handle {
  print("task is run for more than 1 second")
}

丢弃 promise

可以通过调用 drop 方法来丢弃 Promise。 然后,如果当前任务尚未完成,它将发出错误并跳过该任务。 您始终可以在丢弃时传递自定义错误,以便它发出该错误而不是默认错误。

let promise = runPromise {
    print("will be dropped")
}

promise.drop()

请记住,这只会丢弃当前的 Promisefinally 块和 handle 块仍将被调用。

let promise = runPromise {
    print("will not be dropped")
}.then {
    print("will be dropped")
}.handle { error in
    print("will still be executed")
}.finally { result, error in
    print("will still be executed")
}

promise.drop()

继续使用其他 Promise

您可以使用新的 promise 继续 then。 使用 thenContinue 代替 then,然后返回一个 Promise

runPromise {
    // do something
}.thenContinue {
    return somethingThatReturnAPromise()
}.then {
    print("will executed after promise from somethingThatReturnAPromise() is finished"
}

组合 Promise

您可以组合最多 3 个 Promise,使其成为 Tuple 的单个 Promise 作为结果。

let firstPromise = runPromise {
  return "from first Promise"
}
let secondPromise = runPromise {
  return "from second Promise"
}

waitPromises(from: firstPromise, secondPromise).then { result in
  // will print "from first Promise, from second Promise"
  print("\(result.1),\(result.2)")
}

由于 waitPromises 实际上只是返回一个 TuplePromise,因此您可以始终将其视为常规的 Promise

Promise 状态

您可以随时使用其对象检查 Promise 状态。 它有一些属性可以检查。

let promise = runPromise {
  print("I'm running in the DispatchQueue.global(qos: .background)")
}.then {
  print("I'm running on the same DispatchQueue as previous")
}

print(promise.isCompleted)

新的 Swift Async

Swift 引入了新的功能,即 async。 Ergo 也可以与新的 async 一起使用。 要从 async 方法创建 Promise,请使用全局 asyncAwaitPromise

asyncAwaitPromise {
  await myAsyncFunction()
}.then { result in
  print(result)
}

如果您想将 Promise 视为 async,只需使用 Promise 中的 result 属性。

let asyncResult = try await myPromise.result

它将在完成后返回结果,如果发生错误则抛出错误。

您也可以随时将 Task 转换为 Promise

let promiseFromTask = myTask.asPromise()

或将 Promise 转换为 Task

let taskFromPromise = myPromise.asTask()

请记住,所有 async 功能仅在 macOS 10.15、iOS 13.0、watchOS 6.0 和 tvOS 13.0 上可用。

使用异步任务创建 Promise

有时,您要转换为 Promise 的任务已经是异步任务。 在这种情况下,您可以使用 asyncPromise 代替 runPromise

asyncPromise(on: .main) { consumer in
  doSomethingAsync { result, error in
    if let error = error {
        consumer.reject(error)
    } else if let result = result {
        consumer.resolve(result)
    }
  }
}.then { result in
  print(result)
}.handle { error in
  print(error)
}

如果 done 参数获得 nil 结果,或者 nil 以外的错误,它将发出错误。 如果结果不为 nil,它将运行下一个 Promise 任务。 asyncPromise 的结果是 Promise,因此您始终可以将其视为常规的 Promise

链式动画 (仅限 iOS)

您可以使用 ChainAnimator 运行动画,它可以像 Promise 一样链接。

UIView.chainAnimate(withDuration 0.2)
  .animation {
    view.alpha = 0.5
  }.chain(withDuration: 0.2) {
    view.alpha = 1
  }.animate()

它将从第一个动画开始运行,并在最后一个动画完成后继续执行下一个动画。 您可以根据需要链接尽可能多的动画。 animate 的结果是 BoolPromise。 如果所有动画都成功,则 Bool 结果将为 true。

UIView.chainAnimate(withDuration 0.2)
  .animation {
    view.alpha = 0.5
  }.chain(withDuration: 0.2) {
    view.alpha = 1
  }.animate()
  .then { succeed in
    print(succeed)
  }

由于结果是常规的 Promise,因此您可以始终将其视为常规的 Promise

贡献

您知道怎么做,只需克隆并进行 pull request 即可