借款人 1.0.0

借款人 1.0.0

Ennio Bovyn维护。



借款人 1.0.0

  • Ennio Bovyn

借款人

CI Status Version License Platform

借款人是一个Swift中的Promise实现。

关于Promise

Promise代表了异步操作最终完成或失败的结果。Promise与现实生活中的承诺非常相似,一个承诺要么实现,要么失败。

异步编程:从回调到Promise

在Promise之前,回调型的API常用于异步代码。以下是一个示例

// Callback hell
func doSomethingAsync(parameters: [String: Any], completion: @escaping (Response?, Error?) -> ()) {
    validate(parameters) { _, error in
        if let error = error {
            completion(nil, error)
        } else {
            self.queryDB(with: parameters) { dbResult, error in
                if let error = error {
                    completion(nil, error)
                } else if let dbResult = dbResult {
                    self.doServiceCall(dbResult) { response, error in
                        completion(response, error)
                    }
                }
            }
        }
    }
}

这种方式使用深度嵌套回调的特定模式通常被称为"回调地狱",因为它使代码可读性变差,且难以维护。

现在,使用Promise

// Promise
func doSomethingAsync(parameters: [String: Any]) -> Promise<Response> {
    return validate(parameters)
        .then {
            self.queryDB(with: parameters)
        }
        .then { dbResult in
            self.doServiceCall(dbResult)
        }
}

使用方法

Promise是一个对象,表示异步操作的最终完成或失败。

本质上,Promise就是一个返回的对象,你可以将处理器(又称回调)附加到它,而不是将处理器传递给函数。

想象有一个函数,createAudioFileAsync(),它异步地根据配置记录生成声音文件,并给定两个处理器函数,一个在成功创建音频文件时调用,另一个在发生错误时调用。

以下是一些使用createAudioFileAsync()的代码

func handleSuccess(url: URL) {
    print("Audio file ready at URL:", url)
}

func handleFailure(error: Error) {
    print("Error generating audio file:", error)
}

createAudioFileAsync(settings: audioSettings, successHandler: handleSuccess, failureHandler: handleFailure)

...使用借款人,你可以让函数返回一个Promise,你可以将处理器附加到这个Promise上

如果将 createAudioFileAsync() 重新编写为返回一个承诺(Promise),使用它可能会像这样简单

createAudioFileAsync(settings: audioSettings).then(handleSuccess, handleFailure)

这是简写形式:

let promise = createAudioFileAsync(settings: audioSettings)
promise.then(handleSuccess, handleFailure)

我们称这种为异步函数调用。这种约定有几个优点。我们将逐一探讨。

创建一个承诺(Promise)

可以使用初始化器从头开始创建一个承诺。

let promise = Promise<String> { resolve, reject in
    resolve("Yay, my first promise!")
}

您还可以使用初始化器封装一个基于完成处理程序(completion handler)的 API,如 URLSession

let promise = Promise<Data> { resolve, reject in
    session.dataTask(with: request, completionHandler: { data, response, error in
        if let error = error {
            reject(error)
        } else if let data = data {
            resolve(data)
        } else {
            fatalError()
        }
    }).resume()
}

基本上,承诺初始化器接收一个执行器(executor)函数,该函数允许我们手动解析或拒绝一个承诺。

保证

与“老式”传入处理程序(称为回调函数,callback)不同,承诺(Promise)提供了一些保证

  • 使用 then()catch()finally() 添加的处理程序甚至会在异步操作完成或失败后调用。

  • 通过多次调用 then()catch()finally() 可以添加多个处理程序。每个处理程序将按照它们插入的顺序依次执行。

使用承诺的其中一个优点是 链式调用(chaining)

链式调用

需要执行两个或更多异步操作连续执行的情况非常常见,其中每个后续操作都是在前一个操作成功后开始的,下一操作将使用前一步的结果。我们通过创建 承诺链(promise chain) 来实现这一点。

这里的魔法在于,then() 函数返回一个 新的承诺(promise),与原始的不同

let promise = doSomething()
let promise2 = promise.then(handleSuccess, handleFailure)

或者

let promise2 = doSomething().then(handleSuccess, handleFailure)

或者

let promise2 = doSomething()
    .then { value
        handleSuccess(value)
    }
    .catch { error in
        handleFailure(error)
    }

第二个承诺(promise2)不仅代表 doSomething() 的完成,还代表你传入的 handleSuccesshandleFailure 的完成,这可以是被其他返回承诺的异步函数。在这种情况下,任何添加到 promise2 的处理程序都将排在 handleSuccesshandleFailure 返回的承诺后面。

基本上,每个承诺都代表链中另一个异步步骤的完成。

从前,连续执行几个异步操作会导致经典的回调金字塔问题

doSomething(successHandler: { result in
    doSomethingElse(result, successHandler: { newResult in
        doThirdThing(newResult, successHandler: { finalResult in
            print("Got the final result:", finalResult)
        }, failureHandler: handleFailure)
    }, failureHandler: handleFailure)
}, failureHandler: handleFailure)

使用承诺,我们将处理程序附加到返回的承诺上,从而形成一个承诺链

doSomething()
    .then { result in
        return doSomethingElse(result)
    }
    .then { newResult in
        return doThirdThing(newResult)
    }
    .then { finalResult in
        print("Got the final result:", finalResult)
    }
    .catch(handleFailure)

try之后串联操作

在失败之后串联操作是可能的,例如在 catch 中,这可以在动作链中失败后执行新的操作。请阅读以下示例

Promise<()> { resolve, reject in
    print("Initial")

    resolve(())
}
.then {
    throw SomeError()

    print("Do this")
}
.catch { _ in
    print("Do that")

    return doThatOnFailure()
}
.then {
    print("Do this instead")
}

如果 doThatOnFailure() 成功完成,它会输出以下文本

Initial
Do that
Do this instead

注意:文本 "Do this" 没有显示,因为 SomeError 错误导致拒绝。

错误传播

你可能记得在"地狱金字塔"中看到过三次 handleFailure(),而在承诺链末尾只有一次

doSomething()
    .then { result in doSomethingElse(result) }
    .then { newResult in doThirdThing(newResult) }
    .then { finalResult in print("Got the final result:", finalResult) }
    .catch(handleFailure)

基本上,如果一个承诺链中出现了异常,它会终止查看链的 catch 处理程序,这与同步代码的模型非常相似

do {
    let result = try doSomethingSync()
    let newResult = try doSomethingElseSync(result)
    let finalResult = try doThirdThingSync(newResult)
    print("Got the final result:", finalResult)
} catch {
    handleFailure(error)
}

承诺通过捕获所有错误(即使抛出的是异常或编程错误)来解决"地狱金字塔"的基本缺陷,这对异步操作的功能组成至关重要。

组合

Promise.resolve()Promise.reject() 是分别手动创建已解析或拒绝的承诺的快捷方式。在有些时候这很有用。

Promise.all()Promise.race() 是执行并行异步操作的两个组合工具。

我们可以并行开始操作并等待它们全部完成,如下所示

Promise.all(books.map { fetchMetadata(for: $0) })
    .then { allMetadata in
        zip(books, allMetadata).forEach { book, metadata in
            print("\(book.title) metadata: \(metadata)")
        }
    }

安装

Promisor 通过 CocoaPods 可用。要安装它,只需将以下行添加到您的 Podfile

pod 'Promisor'

作者

Ennio Bovyn, [email protected]

许可协议

Promisor可在MIT许可下使用。更多信息请参阅LICENSE文件。