Home > OS >  How do we wait for an async/await function run completely and then run the following line of code in
How do we wait for an async/await function run completely and then run the following line of code in

Time:12-24

How do we wait for an async/await function and then run the following line of code in Swift?

await res.checkExistingUser(email: loginForm.email)
print("after that") // it doesn't wait for the above function to run completely

in the res.checkExistingUser() is used for calling API and mapping the model.

@MainActor
final class LoginViewModelImpl: LoginViewModel {
    @Published var hasError = false
    @Published private(set) var errorMesg = ""
    @Published private(set) var isFetching = false
    @Published private(set) var isLoggedIn = false
    
    @Published private(set) var isVerify = false
    
    func checkExistingUser(email: String) async {
        self.isFetching = true
        let res = UserServiceImpl()
        
// Call API
        res.checkExistingUser(email: email).responseDecodable(of: BasedResponse<UserExistModel>.self) { response in
            switch response.result {
            case .success(let apiListingResponse):
                print(apiListingResponse)

                if !apiListingResponse.success {
                    self.hasError = true
                    self.errorMesg = String(localized: String.LocalizationValue((apiListingResponse.error?.message)!) )
                    self.isFetching = false
                    break
                }
                
                if (apiListingResponse.payload?.message) != nil {
                    self.isVerify = false
                    self.hasError = true
                    self.errorMesg = String(localized: String.LocalizationValue("verify_email"))
                    self.isFetching = false
                    return
                }
            ...
        }
    }

CodePudding user response:

The problem is likely that res.checkExistingUser is not itself a true async function.

For await to work properly, the method you call must be

(1) Marked async.

(2) Actually be async. This means that all its work must be done either directly or by calling only async methods with await.

In other words, async/await is not magic; it cannot turn what is not async into something that is async. The real work is still up to you.

For example, here's what not to do:

func func1() async {
    print("start 1")
    await func2()
    print("end 1")
}

func func2() async {
    print("start 2")
    DispatchQueue.main.async {
        sleep(1)
        print("finished inner 2")
    }
    print("end 2")
}

That yields this result:

start 1
start 2
end 2
end 1
finished inner 2 (much later)

As you can see, func1 didn't "wait" for everything in func2 to happen. But that's because func2 is not truly async. You cannot combine async/await with other asynchronous approaches, such as DispatchQueue!

Here's a corrected version:

func func1() async {
    print("start 1")
    await func2()
    print("end 1")
}

func func2() async {
    print("start 2")
    do {
        try? await Task.sleep(for: .seconds(1))
        print("finished inner 2")
    }
    print("end 2")
}

That does everything in order, and func1 waits "correctly":

start 1
start 2
finished inner 2
end 2
end 1

So for your code to work, checkExistingUser would need to be itself converted to true async and everything it calls would need to be true async.


Now that you have shown your code, my guess is shown to be correct. checkExistingUser is calling a method res.checkExistingUser that is asynchronous but is not async; on the contrary, it takes a completion handler. You can't do that. Completion handlers and async/await are opposites. That is the whole point of async/await!

So, that method would either need to be converted to true async (i.e. be rewriting it), or it would need to be wrapped in a conversion to true async (which you can do using withCheckedThrowingContinuation, as I explain in enter image description here

Now, you didn't share the function signature of responseDecodable, but for illustrative purposes, let us assume it was the following:

func responseDecodable<T: Decodable>(of dataType: T.Type = T.self, completion: @escaping (BasedResponse<T>) -> Void) {
    …
}

Then you can add a wrapper implementation:

func responseDecodable<T: Decodable>(of dataType: T.Type = T.self) async -> BasedResponse<T> {
    await withCheckedContinuation { continuation in
        responseDecodable(of: dataType) { result in
            continuation.resume(returning: result)
        }
    }
}

Clearly, if your responseDecodable was different, just modify that wrapper implementation accordingly. (Or let “Refactor” » “Add Async Wrapper” feature do this for you.) And if responseDecodable is from some library you cannot edit, do not worry: Just put your async rendition in an extension of its original type in your own codebase.

Anyway, you can now replace the incorrect checkExistingUser:

func checkExistingUser(email: String) async {
    self.isFetching = true
    let res = UserServiceImpl()
    
    res.checkExistingUser(email: email).responseDecodable(of: BasedResponse<UserExistModel>.self) { response in
        switch response.result { … }
    }
}

With a rendition that will await your new async implementation of responseDecodable:

func checkExistingUser(email: String) async {
    self.isFetching = true
    let res = UserServiceImpl()
    
    let response = await res.checkExistingUser(email: email).responseDecodable(of: BasedResponse<UserExistModel>.self)

    switch response.result { … }
}

You now have an implementation of checkExistingUser which will properly await the result.


Frankly, this process of wrapping a completion handler rendition with an async rendition is merely an interim solution. You really want to eventually retire all of your completion handler implementations, altogether. Personally, as advised in that video, the pattern I've used in my projects is as follows:

  • add an async wrapper rendition;
  • add a deprecation warning to the completion handler rendition;
  • fix all the calling points that are using the completion handler rendition to follow async-await patterns; and, when all done with that
  • go back and
    • refactor the completion handler rendition to follow async-await, itself
    • retire the wrapper rendition I wrote earlier

Note, I did not have a reproducible example of your issue, so I made some assumptions in the above. So, do not get bogged down in the details. The idea is to wrap/refactor your completion handler method with an async one that uses withCheckedContinuation (or withThrowingCheckedContinuation), and now you can await a function that formerly had a completion handler.

  • Related