I am having a hard time learning the new Swift async functions and getting them to perform in the correct order.
I want to make my loadData() function "await" or WAIT when calling it but when printing the order that should be 1,2,3 it prints out as 1,3,2 which means after calling the function it goes to the next line of code instead of waiting for the results of vm.loadData()
I am getting the warning "No ‘async’ operations occur within ‘await’ expression" where I try to make the call but I thought by putting the call in a task would make it work?
Below is my code, what am I doing wrong?
VIEW
struct Button: View {
let vm = ViewModel()
var body: some View {
Circle().onTapGesture { doSomething }
}
func doSomething() {
print("1")
Task {
// WARNING: No 'async' operations occur within 'await' expression
await vm.loadData()
}
print("3")
}
}
VIEW MODEL
class ViewModel: ObservableObject {
@Published var data: [String] = []
func loadData() {
Task {
do {
let dataArray = try await DataApi.loadData()
if !dataArray.isEmpty {
self.data = dataArraay
}
print("2")
} catch {
<HANDLE ERRORS>
}
}
}
}
API CALL
class DataApi {
static func loadData() async throws -> [String] {
// MAKE API CALL
do {
// decode data
return data
} catch {
throw ResponseError.decodingError("(error)")
}
return []
}
}
2
Answers
vm.loadData()
is not an async method, so the compiler is complaining that you call it withawait
in your SwiftUI view.At its most basic, Swift’s async rule is: any time you use the
await
keyword, you must be calling a method that’s declared withasync
, and if you declare a method withasync
, you must useawait
when you call it.In
ViewModel
, you’re usingTask
correctly as a "bridge" between synchronous mode and asynchronous mode. You callvm.loadData()
synchronously, and an async task calls the async API; when the data comes back, it stores the returned value in a property.Creating that Task returns immediately, so
vm.loadData()
finishes executing while the task is still running.That means that your call to
vm.loadData()
doesn’t need to be wrapped in another task. You can call it directly, and it’ll create the background async work for you. That simplifies your calling code:await
means only that you can proceed with the result of the line within the task. It does not mean that the enclosing task is synchronous.The warning is displayed because
loadData
inViewModel
is notasync
.This is a complete asynchronous version
ViewModel
API call
It’s recommended to hand over errors to the caller when marked with
throws
. The API call is supposed to throw theResponseError