@SinceKotlin("1.3")
@InlineOnly
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()
}
}
suspendCoroutine
은 callback 을 실행한 후 suspendFunction 을 실행한 부분에서 해당 coroutine 을 중단한다.
suspend fun main() {
println("Before")
delay(1)
suspendCoroutine<Unit> { continuation ->
println("Before too")
}
println("After")
}
위 코드는
Before
Before too
까지만 출력되고 멈춘다.
이후 callback 에서 continuation.resume
을 호출했을 때, 멈춘 부분부터 coroutine 을 재개한다.
참고 문서: Stack overflow - What is suspendCoroutine?
suspendCoroutine
을 이용하면 기존의 callback 으로 구현된 비동기 함수들을 suspend 형식으로 바꿀 수 있다.
마치 node.js 에서 Promise 로 구현된 callback 지옥을 async/awiat 으로 풀 수 있었던 것처럼 Kotiin 에서도 그런 게 suspendCoroutine
을 이용하면 가능해진다.
즉, 아래와 같은 코드가
Api.getUser(id) { user ->
Api.getProfile(user) { profile ->
Api.downloadImage(profile.imageId) { image ->
// ...
}
}
}
아래와 같이 flat 해질 수 있다.
val user = getUser(id)
val profile = getProfile(user)
val image = downloadImage(profile.imageId)
//...
이게 어떻게 가능해지는지 조금 더 들어가보면, 가령 아래와 같은 코드가 있다고 해보자.
fun getUser(id: String, callback: (User) -> Unit) {...}
여기서 suspendCoroutine
을 이용하여 suspend function 으로 다시 쓸 수 있다.
suspend fun getUser(id: String): User = suspendCoroutine { continuation ->
Api.getUser(id) { user ->
continuation.resume(user)
}
}
위 코드는 suspendCoroutine 의 lambda 블록을 실행하고 진행 중이던 coroutine 을 중단한다.
lambda 블록은 실행 중에 비동기 API 인 Api.getUser 를 호출한다.
getUser 의 응답이 오고 callback 함수가 실행된다.
이 callback 함수에서 continuation.reusme(user)
를 실행함으로서, 응답 결과인 user 를 가지고 중단되었던 coroutine 을 재개한다.