As you've noted in your comments to matt, this basic approach is not likely what you want. This always kicks off both requests, and so always has to wait for both to finish before returning (even if you never need them both).
You can get something like what you want this way, but it's pretty messy (which is why I suspect they haven't integrated it yet):
Create a new operator ¿¿ that combines async operations:
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() }
}
Your default value should be in a closure so it doesn't run automatically:
let langRes = try await traverse(file: languageFile, for: string)
let engRes = { try await traverse(file: englishFile, for: string) }
And then you could do what you want (returning a String):
let result = try await langRes ¿¿ engRes ?? "default"
That said, I'd say that anything that tries to solve this with a ??-like operator is going to wind up being very subtle and easy to accidentally kick off tasks you didn't mean. Writing it matt's way makes it much more obvious that what you're doing is actually wrong.
Another similar approach would be:
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" }
That said, I'd probably consider just extracting a loop instead:
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)
I believe it should be be possible to create something like:
let result = try await [langRes, englishRes].lazy
.compactMap({try await traverse($0, for: string)})
.first ?? "default"
But I don't think AsyncSequence is quite up to that yet, without adding a lot of extensions. IMO, mapping a Sequence with an async
closure should create an AsyncMapSequence. But it currently doesn't. Also, it feels like it should be possible to call first
on an AsyncSequence, but that's also not possible (you'd have to call first(where: {true})
or something silly like that as best I can tell).
(Agreed with matt that this is a great question. This is worth discussing on the Swift forum. It's the kind of thing that should have a clear pattern.)
await
when you sayasync let
. You sayawait
later when you pick up the value. – SamfordoptionalString ?? optionalString
even in the normal world, so your??
makes no sense. – Samford?? “”
on the end. – Fjord