I need to call a suspending function inside a suspendCoroutine block, before I call continuation.resume().
What is the appropriate way of doing that?
private suspend fun someFunction() = suspendCoroutine { cont ->
//...
val myResult = mySuspendingFunction() //<--- The IDE says "Suspension functions can be called only within coroutine body"
cont.resume(myResult)
}
You can't call a suspend function in suspendCoroutine block, because it accepts non suspend block as parameter:
suspend inline fun <T> suspendCoroutine(
crossinline block: (Continuation<T>) -> Unit
): T
'suspendCoroutine' mainly used when we have some legacy code with callbacks, e.g.:
suspend fun getUser(id: String): User = suspendCoroutine { continuation ->
Api.getUser(id) { user ->
continuation.resume(user)
}
}
If function someFunction() doesn't call Api with callbacks then you should reconsider your approach getting rid of 'suspendCoroutine':
private suspend fun someFunction() {
// ...
val myResult = mySuspendingFunction()
// ...
}
If you still want to use suspendCoroutine move call of mySuspendingFunction out of suspendCoroutine block:
private suspend fun someFunction(): String {
val myResult = mySuspendingFunction()
return suspendCoroutine { cont ->
//...
cont.resume(myResult)
}
}
suspend fun mySuspendingFunction(): String {
delay(1000) // simulate request
return "result"
}
It's best to avoid this and call the suspending function before suspendCoroutine, as others have answered. That is possible for the specific case in question.
However, that is not possible if you need the continuation.
(The following is for those, who found this question for the this reason, as @Zordid and I have. chan.send is an example of this.)
In which case, the following is a possible, but error prone way to do it, that I do not recommend:
suspend fun cont1() {
//btw. for correct implementation, this should most likely be at least suspendCancellableCoroutine
suspendCoroutine<Unit> { uCont ->
val x = suspend { chan.send(foo(uCont)) }
x.startCoroutine(Continuation(uCont.context) {
if (it.isFailure)
uCont.resumeWith(it)
// else resumed by whatever reads from chan
})
}
}
(I think the error handling alone illustrates why it's not a great option, despite other problems.)
A better, safer and cheaper way is to use CompletableDeferred if you can.
If you must pass in a Continuation, it's still safer and probably cheaper to do:
suspend fun cont2() {
val rslt = CompletableDeferred<Unit>()
chan.send(foo(Continuation(currentCoroutineContext()) {
rslt.completeWith(it)
}))
rslt.await()
}