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
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
- refactor the completion handler rendition to follow
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.