PinkyPromise 0.7.1

PinkyPromise 0.7.1

测试已测试
语言 SwiftSwift
许可证 MIT
发布最后发布2020年4月
SPM支持 SPM

Kevin ConnerAndrew Carter 维护。



PinkyPromise

一个小型的 Promises 库。

Circle CI Coverage Status

概述

PinkyPromise 是 Swift 中实现 Promises 的一个实现。它包含两种类型

  • Result - 一个值或错误。我们使用 Swift 内置的 Result 类型
  • Promise - 一个在调用后会在某个时间点产生一个 Result 的操作。Promises 可以组合和排序。

使用 PinkyPromise,您可以用安全、干净、Swift 式的代码运行复杂的异步操作组合。

安装

  • 使用 Carthage: github "willowtreeapps/PinkyPromise"
  • 使用 Cocoapods: pod 'PinkyPromise'
  • 手动: 将 Sources 文件夹中的文件复制到您的项目中。

我应该使用这个吗?

PinkyPromise

  • 轻量级
  • 经过测试
  • 支持 Swift 语言,具有严密的类型系统合约以及 throw / catch
  • 支持函数式风格,使用不可变值和值转换
  • Objective-C 程序员学习 Swift 函数式风格的好方法
  • 可以扩展您自己的 Promise 转换

PinkyPromise 旨在成为一款轻量级工具,但它能完成很多繁重的工作。更复杂的实现包括 ResultPromiseKit

学习

从下面的示例部分开始。

我们还编写了一个游乐场来展示 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> 是一个具有两个情况的自定义枚举:`Success(T)` 和 `Failure(Error)`。可以使用枚举情况创建一个 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捕获了它的任务,而任务捕获了它的参数。这是一个等待开始的操作。因此,使用Promise,你可以创建操作并在以后再开始它们。你可以多次开始它们,也可以一次也不开始。

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

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

正如我们所看到的,在Promise中,提供参数和提供完成块是两个不同的事件。Promise最大的优势是,在这两个事件之间,要做的任务作为不可变值存在。在函数式样式中,不可变值可以被转换和组合。

这是一个由多个Promise组成的复杂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中运行测试,或使用bundle exec fastlane run_testsFastlane

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

路线图

  • 更多Promise转换?

为Pinky Promise做出贡献

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

PinkyPromise已采用行为准则,该准则由贡献者誓言定义,与Swift语言和无数其他开源软件团队使用相同。