skip to Main Content

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


  1. vm.loadData() is not an async method, so the compiler is complaining that you call it with await 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 with async, and if you declare a method with async, you must use await when you call it.

    In ViewModel, you’re using Task correctly as a "bridge" between synchronous mode and asynchronous mode. You call vm.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:

    Circle().onTapGesture { vm.loadData() }
    
    Login or Signup to reply.
  2. 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 in ViewModel is not async.

    This is a complete asynchronous version

    func doSomething() {
       print("1")
       Task {
           await vm.loadData()
           print("3")
       }
    }
    

    ViewModel

    @MainActor
    class ViewModel: ObservableObject {
    
         @Published var data: [String] = []
        
         func loadData() async {
             do {
                 self.data = try await DataApi.loadData()
             } catch {
                 self.data = []
                 print(error)   
             }
             print("2")
         }
    }
    

    API call

    class DataApi {
        static func loadData() async throws -> [String] {
             // try await MAKE API CALL
             // try decode data
             return data
        }
    }
    

    It’s recommended to hand over errors to the caller when marked with throws. The API call is supposed to throw the ResponseError

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search