4

我有一个async返回可选的函数String

func traverse(file:URL, for string:String) async throws -> String? {
...
}

我想运行这个函数,如果nil,使用不同的参数运行它,如下所示:

async let langRes = try traverse(file: languageFile, for: string)
async let engRes = try traverse(file: englishFile, for: string)
let result = try await langRes ?? engRes

但可选的默认值engRes会给出错误:

'async let' 在不支持并发的自动关闭中

像这样的东西很好用:

let result = try await langRes ?? ""

它只是不喜欢使用async默认/后备功能。

我知道我可以这样做:

var result = try await langRes
if result == nil {
    result = try await engRes
}

但最好使用可选的链接回退??

我的语法是错误的还是它不起作用?

4

2 回答 2

2

你是完全正确的:你不能使用??你想要的方式。第二个成员(在 之后??)确实是一个自动关闭,因此除非必须这样做,否则不会对其进行评估。但是你不能在使用await. 因此,正如您所建议的那样,您将不得不接受它并使用更笨拙的说话方式。就个人而言,我可能会写:

    async let langRes = traverse(file: languageFile, for: string)
    async let engRes = traverse(file: englishFile, for: string)
    let result : String
    if let temp = try await langRes { result = temp }
    else if let temp = try await engRes { result = temp }
    else { result = "" }

但是请注意,您必须决定这是否真的是一个很好的使用async let,因为您正在做的是启动两个并发进程,langRes无论engRes第一个进程是否成功地让您获得非零结果。在我看来,你想要的实际上可能是连续的await调用,这样如果第一个成功,你甚至不会启动第二个。

于 2021-08-02T16:46:52.673 回答
1

正如您在对马特的评论中指出的那样,这种基本方法可能不是您想要的。这总是会启动两个请求,因此总是必须等待两个请求都完成才能返回(即使您从不需要它们两个)。

你可以通过这种方式得到你想要的东西,但它非常混乱(这就是为什么我怀疑他们还没有集成它):

创建一个新的运算符 ¿¿ 组合异步操作:

precedencegroup AsyncNilCoalescingPrecedence {
    associativity: right
    higherThan: NilCoalescingPrecedence
}

infix operator ¿¿ : AsyncNilCoalescingPrecedence

public func ¿¿<T>(optional: T?, defaultValue: () async throws -> T?) async rethrows -> T? {
    if let result = optional { return result } else { return try await defaultValue() }
}

您的默认值应该在一个闭包中,因此它不会自动运行:

let langRes = try await traverse(file: languageFile, for: string)
let engRes = { try await traverse(file: englishFile, for: string) }

然后你可以做你想做的事(返回一个字符串):

let result = try await langRes ¿¿ engRes ?? "default"

也就是说,我想说的是,任何试图用类似 ?? 的操作符来解决这个问题的东西最终都会变得非常微妙,并且很容易意外地启动你不想要的任务。以马特的方式编写它可以更加明显地表明您正在做的事情实际上是错误的。

另一种类似的方法是:

let result: String
if let r = try await traverse(file: languageFile, for: string) { result = r }
else if let r = try await traverse(file: englishFile, for: string) { result = r }
else { result = "default" }

也就是说,我可能会考虑只提取一个循环:

func traverse(files: [URL], for string:String) async throws -> String {
    for file in files {
        if let result = try await traverse(file: file, for: string) { return result }
    }
    return "default"
}

let result = traverse([langRes, englishRes], for: string)

我相信应该可以创建类似的东西:

let result = try await [langRes, englishRes].lazy
    .compactMap({try await traverse($0, for: string)})
    .first ?? "default"

但我不认为 AsyncSequence 还没有添加很多扩展。IMO,用async闭包映射序列应该创建一个 AsyncMapSequence。但目前还没有。此外,感觉应该可以调用firstAsyncSequence,但这也是不可能的(你必须调用first(where: {true})或类似的愚蠢的东西,据我所知)。

(同意马特这是一个很好的问题。这值得在 Swift 论坛上讨论。这种事情应该有一个清晰的模式。)

于 2021-08-02T19:45:57.300 回答