使用 Promises 的代码根本不执行

Code using Promises doesn't execute at all

为了这个我花了大半天的时间用头撞墙,我想我终于明白了。这是我今天早上开始寻找时希望存在的问题。

作为背景,我有多年使用 C++ 和 Python 的经验,最近我开始学习 Swift 非 iOS 开发。我将在此处展示的所有内容在 MacBook Pro 上的表现似乎与在我的 Ubuntu PC 上的表现相同。我正在 运行ning Swift 5.4,在命令行中编译和 运行 Swift 包管理器。

我已经通读了 Swift 中关于使用 Promises 的几篇文章,但并没有加起来。他们展示的示例让您看起来好像可以调用 firstly,将几个 .then 调用链接在一起,然后用 .done.catch 将它们全部连接起来,一切都会正常进行。当我尝试这个时,我大多会遇到一堆错误,但有时我很幸运能够编译它,却发现当我 运行 它时什么也没有发生。

这里有一个示例 main.swift 文件来说明我的困惑。

import PromiseKit

firstly {
    Promise<String> { seal in
        print("Executing closure")
        seal.fulfill("Hello World!")
    }
}.done { str in
    print(str)
}

当我运行这个时,它只打印Executing closure,即使我在它后面添加一个睡眠,或者如果我在结果上调用.wait()。为什么 firstly 块执行而不是 done 块?

这是另一个。我花了一段时间才编译它,但它仍然没有达到我的预期:

import Foundation
import PromiseKit

let url = URL(string: "https://whosebug.com/")!

enum SampleError: Error {
    case noResponse
    case badCode(Int)
    case couldNotDecode
}

firstly {
    Promise<Data> { seal in
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                return seal.reject(error)
            }

            guard let data = data, let response = response as? HTTPURLResponse else {
                return seal.reject(SampleError.noResponse)
            }

            guard (200..<300).contains(response.statusCode) else {
                return seal.reject(SampleError.badCode(response.statusCode))
            }

            seal.fulfill(data)
        }.resume()
    }
}.then { data -> Promise<String> in
    Promise<String> { seal in
        if let str = String(data: data, encoding: .utf8) {
            seal.fulfill(str)
        } else {
            seal.reject(SampleError.couldNotDecode)
        }
    }
}.done { str in
    print(str)
}.catch { error in
    print("Error: \(error)")
}

我希望它打印 Stack Overflow 主页的 HTML 内容,但它什么也没打印。

看来我已经准备好了所有设置,但 Promise 还没有 运行ning。如何让它们真正执行?

这里的问题似乎是所有在线文章都专注于 iOS 开发,并没有真正解决 desktop/server 可执行文件中的 Promise。简单的脚本旨在终止,而移动应用程序旨在无限期地 运行。

如果你深入研究 Thenable 代码,你会发现 .then.done.catch 等都有一个 [=16] 的参数=],深入挖掘,默认为 DispatchQueue.main。因为这些示例中的顶级代码正在使用主线程,所以主 DispatchQueue 永远没有机会执行分配给它的工作。通过在最后调用 dispatchMain() 将主线程交给调度系统,这两个示例都可以很容易地执行 Promise 链。

但是,dispatchMain() 永远不会 returns,因此对于旨在 运行 完成的可执行文件来说,这不是一个好的选择。更好的解决方案是指定一个备用队列来执行这些承诺中的每一个。对于单个任务,我们可以在 .finally 块中使用信号量来表示所有工作已完成。对于多项任务,我怀疑您需要使用 DispatchGroup.

这是第一个示例的更新版本:

import Foundation
import PromiseKit

let queue = DispatchQueue.global()
let semaphore = DispatchSemaphore(value: 0)

firstly {
    Promise<String> { seal in
        print("Executing closure")
        seal.fulfill("Hello World!")
    }
}.done(on: queue) { str in
    print(str)
}.catch(on: queue) { error in
    print("Error: \(error)")
}.finally(on: queue) {
    semaphore.signal()
}

semaphore.wait()

这是第二个的工作版本:

import Foundation
import PromiseKit

let queue = DispatchQueue.global()
let semaphore = DispatchSemaphore(value: 0)

let url = URL(string: "https://whosebug.com/")!

enum SampleError: Error {
    case noResponse
    case badCode(Int)
    case couldNotDecode
}

firstly {
    Promise<Data> { seal in
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                return seal.reject(error)
            }

            guard let data = data, let response = response as? HTTPURLResponse else {
                return seal.reject(SampleError.noResponse)
            }

            guard (200..<300).contains(response.statusCode) else {
                return seal.reject(SampleError.badCode(response.statusCode))
            }

            seal.fulfill(data)
        }.resume()
    }
}.then(on: queue) { data -> Promise<String> in
    Promise<String> { seal in
        if let str = String(data: data, encoding: .utf8) {
            seal.fulfill(str)
        } else {
            seal.reject(SampleError.couldNotDecode)
        }
    }
}.done(on: queue) { str in
    print(str)
}.catch(on: queue) { error in
    print("Error: \(error)")
}.finally (on: queue) {
    semaphore.signal()
}

semaphore.wait()

有趣的是,firstly块似乎在主线程上同步执行。我突然想到,如果你有很多任务要 运行,最好在 firstly 处理程序中做尽可能少的工作,并释放你的主线程以便它可以自旋更快地启动其他线程上的任务。