过时说明

PinkyPromise 已成功退役。它的功能已由 Swift 语言和第一方库实现——这是长期支持的最佳情况。

如果您一直是 PinkyPromise 用户,请考虑您的迁移路径

感谢您使用 PinkyPromise!

PinkyPromise

一个微型的 Promises 库。

摘要

PinkyPromise 是 Swift 的 Promises 实现。它由两种类型组成

使用 PinkyPromise,您可以用安全、简洁、Swift 风格的代码运行异步操作的复杂组合。

安装

我应该使用它吗?

PinkyPromise

PinkyPromise 旨在成为一个轻量级工具,可以完成大量繁重的工作。更精细的实现包括 ResultPromiseKit

学习

从下面的 示例 部分开始。

我们还编写了一个 playground 来演示 PinkyPromise 的优势和用法。请克隆存储库并在 Xcode 中打开 PinkyPromise.playground

超越 Results 和 Promises 的自然下一步是 Observable 类型。您可以使用 PinkyPromise 作为学习 RxSwift 的第一步,我们推荐这样做。

示例

Promise 最适合运行可以成功或失败的异步操作。

Result 最适合表示此类操作的成功或失败。

为什么选择 Result?

iOS 上常见的异步操作模式是一个函数,它接受参数和一个完成块,然后开始工作。完成块将在工作完成时接收一个可选值和一个可选错误

func getString(withArgument argument: String, completion: ((String?, ErrorType?) -> Void)?) {
    
    if successful {
        completion?(value, nil)
    } else {
        completion?(nil, error)
    }
}

getString(withArgument: "foo") { value, error in
    if let value = value {
        print(value)
    } else {
        print(error)
    }
}

这是一个编译器不保证的松散契约。我们只是假设当 value 为 nil 时,error 不为 nil。

与标准的 Swift 可失败同步方法模式进行比较:像 Data(contentsOf:options:) 这样的函数将返回一个值或抛出一个错误,而不是两者都返回,也不是两者都不返回,也不是可选的。这是一个严密的契约。但是您不能在异步调用中使用该模式,因为您只能向后抛出您所在的函数,而不能向前抛出到完成块中。

以下是如何使用 Result 编写具有更严格契约的异步操作。Result 是成功或失败。它可以使用 returnthrow 创建,并使用 value 检查,这将返回或抛出。

func getStringResult(withArgument argument: String, completion: ((Result<String, Error>) -> Void)?) {
    
    completion?(Result {
        if successful {
            return value
        } else {
            throw error
        }
    })
}
 
getStringResult(withArgument: "foo") { result in
    do {
        print(try result.get())
    } catch {
        print(error)
    }
}

在底层,Result<T, Error> 是一个具有两种情况的 enum.Success(T).Failure(Error)。可以使用枚举 case 创建 Result,并使用 switch 检查它。但是由于 Result 表示返回的值或抛出的错误,我们更喜欢以上述风格使用它。

为什么选择 Promise?

Promises 对于将多个异步操作组合成一个非常有用。为此,我们需要能够创建一个异步操作,而无需立即启动它。

要创建一个新的 Promise,您需要使用一个任务来创建它。任务本身是一个块,它接受一个完成块,通常称为 fulfill。Promise 运行任务来完成其工作,当它完成时,任务将一个 Result 传递给 fulfill。(提示:用于创建此 Promise 的任务与 getStringResult(withArgument:) 的主体相同。)

func getStringPromise(withArgument argument: String) -> Promise<String> {
    return Promise { fulfill in
        
        fulfill(Result {
            if successful {
                return value
            } else {
                throw error
            }
        })
    }
}

let stringPromise = getStringPromise(withArgument: "bar")

stringPromise 捕获了它的任务,并且该任务捕获了参数。它是一个等待开始的操作。因此,使用 Promises,您可以创建操作,然后在稍后启动它们。您可以多次启动它们,或者根本不启动。

接下来,我们通过将完成块传递给 call 方法来要求 stringPromise 运行。call 运行任务并将 Result 路由回完成块。当 Promise 完成时,我们的完成块将接收到 Result,并且可以使用 trycatch 获取值或错误。

stringPromise.call { result in
    do {
        print(try result.get())
    } catch {
        print(error)
    }
}

正如我们所见,使用 Promises,提供参数和提供完成块是独立的事件。Promise 的最大优势在于,在这两个事件之间,待完成的任务作为一个不可变的值存在。在函数式风格中,不可变值可以被转换和组合。

这是一个由多个 Promises 组成的复杂 Promise 的示例

let getFirstThreeChildrenOfObjectWithIDPromise =
    getStringPromise(withArgument: "baz") // Promise<String>
    .flatMap { objectID in
        // String -> Promise<ModelObject>
        Queries.getObjectPromise(withID: objectID)
    }
    .map { object in
        // ModelObject -> [String]
        let childObjectIDs = object.childObjectIDs
        let count = max(3, childObjectIDs.count)
        return childObjectIDs[0..<count]
    }
    .flatMap { childObjectIDs in
        // [String] -> Promise<[ModelObject]>
        zipArray(childObjectIDs.map { childObjectID
            // String -> Promise<ModelObject>
            Queries.getObjectPromise(withID: childObjectID)
        })
    }

getFirstThreeChildrenOfObjectWithIDPromise 是一个单一的异步操作,它由许多小的操作组成。它

  1. 尝试获取对象 ID 的字符串。
  2. 如果成功,则为具有该 ID 的对象运行 API 请求。
  3. 如果成功,则从对象中收集最多三个子 ID。
  4. 如果成功,则为每个子对象运行同步请求,生成一个数组。
  5. 产生最多三个子对象的列表,或来自该过程任何步骤的错误。

即使此操作有许多步骤依赖于先前操作的成功,我们也不必通过编写多个完成块来协调它们。相反,我们只需处理最终结果,使用 Result 提供的严格契约

getFirstThreeChildrenOfObjectWithIDPromise.call { [weak self] result in
    do {
        self?.updateViews(withObjects: try result.get())
    } catch {
        self?.showError(error)
    }
}

测试

我们打算保持 PinkyPromise 完全单元测试。

您可以在 Xcode 中运行测试,或者使用带有 Fastlanebundle exec fastlane run_tests

我们在 CircleCI 上运行持续集成。

路线图

为 PinkyPromise 贡献

欢迎贡献。请参阅 贡献指南

PinkyPromise 采用了由 Contributor Covenant 定义的 行为准则,这与 Swift 语言 和无数其他开源软件团队使用的相同。