-
Notifications
You must be signed in to change notification settings - Fork 121
Description
I recently submitted a PR to address a warning. @jkmassel spotted the method I operated in has signature await -> Result<Void, Error>, at which he commented "the use of async -> Result is unfortunate here".
I think what Jeremy is hinting at is that, while evidently compiling correctly, async and Result are somewhat clashing from a functionality point of view.
Background on Result and asycn/await
Prior to async/await, it was useful to use Result in asynchronous method to have a single callback that safely encapsulated both success and failure outcomes:
func someAsyncComputation(_ onCompletion: Result<Int, Error>)With async/await, we have an alternative pattern available
func someComputation() async throws -> IntOf course, the option used in the codebase also compiles, but it feels a bit stuck in the middle.
func someComputation() async -> Result<Int, Error>Here's the difference at call site:
// Result in closure
someAsyncComputation { result in
switch result {
case .success(let value): ...
case .failure(let error): ...
}
}
// async throws
do {
let value = try await someComputation()
// ...
} catch {
// ...
}
// async throws in a function that itself throws to bubble up the error
let value = try await someComputation
// async Result
let result = await someComputation()
switch result {
case .success(let value): ...
case .failure(let error): ...
}Personally, I am a big fan of Result and I love seeing it in use. But I think it works best when chaining functional programming style transformations in railway oriented programming:
result
.map { ... }
.flatMap { ... }
.mapError { ... }
// etcWhere to go from here
At the time of writing, there are 24 matches for async -> Result in the codebase.
I sampled them and I don't see us leveraging Result in a railway oriented programming way. Example see loadUsernameSuggestions(from:) defined as
| func loadUsernameSuggestions(from text: String) async -> Result<[String], Error> |
and used as
woocommerce-ios/Yosemite/Yosemite/Stores/AccountCreationStore.swift
Lines 80 to 85 in d0a66f4
| let usernameSuggestionsResult = await remote.loadUsernameSuggestions(from: base) | |
| guard case let .success(usernameSuggestions) = usernameSuggestionsResult, | |
| let username = usernameSuggestions.first else { | |
| return nil | |
| } | |
| return username |
We could rewrite this to something like
return try? await remote.loadUsernameSuggestions(from: base)?.firstIt's also possible we'll find some calls that ought to remain callback-based and could benefit with Result without async.
I'd love to hear your thoughts on this. Thanks 😄
