suspend
함수가 다른 suspend
함수를 호출하는 아래 예제를 살펴보자findUser
함수에서 findProfile
과 findImage
함수를 호출한다.class UserService {
private val userProfileRepository = UserProfileRepository()
private val userImageRepository = UserImageRepository()
suspend fun findUser(userId: Long): UserDto {
// 0단계 - 초기 시작
// 1차 중단
val profile = userProfileRepository.findProfile(userId)
// 1단계 - 1차 중단 후 재시작
// 2차 중단
val image = userImageRepository.findImage(profile)
// 2단계 - 2차 중단 후 재시작
return UserDto(profile, image)
}
}
Continuation
인터페이스 만들어서 중단과 재시작 단계들을 라벨로 표시해보자.// 점점 더 많은 기능들이 추가될 것이다.
interface Continuation {
}
findUser
메서드 안에서 Continuation
을 익명 클래스로 구현해서 사용할 수 있다.
sm
인 이유는 상태 머신 (state machine)에서 따왔기 때문sm
내부에 결과를 저장하고 label
을 증가시킨다.suspend fun findUser(userId: Long): UserDto {
val sm = object : Continuation {
var label = 0
// label == 2일 때 아래에서 UserDto를 반환하기 위해 필요
var profile: Profile? = null
var image: Image? = null
}
when (sm.label) {
0 -> { // 0단계 - 초기 시작
sm.label = 1
sm.profile = userProfileRepository.findProfile(userId)
}
1 -> { // 1단계 - 1차 중단 후 재시작
sm.label = 2
sm.image = userImageRepository.findImage(sm.profile!!)
}
2 -> { // 2단계 2차 중단 후 재시작
return UserDto(sm.profile!!, sm.image!!)
}
}
}
Continuation
을 전달하며 Callback
으로 활용해야 한다.
Continuation
에 resumeWith
라는 추상 메서드를 만든다.
sm
이 resumeWith
를 오버라이드하며 findUser를 다시 호출할 것이다.suspend
함수 전부에 마지막 매개변수로 Continuation
을 전달해보자interface Continuation {
suspend fun resumeWith(data: Any?)
}
suspend fun findUser(userId: Long, continuation: Continuation?): UserDto {
val sm = continuation ?: object : Continuation { // 무한 루프를 피하기 위해 null인 경우에만 label==0인 sm 생성
var label = 0
var profile: Profile? = null
var image: Image? = null
override suspend fun resumeWith(data: Any?) {
when (label) { // Continuation 내부에서 label을 증가
0 -> {
profile = data as Profile
label = 1
}
1 -> {
image = data as Image
label = 2
}
}
findUser(userId, this) // Continuation 자신을 전달
}
}
when (sm.label) {
0 -> {
userProfileRepository.findProfile(userId, sm) // sm 전달
}
1 -> {
userImageRepository.findImage(sm.profile!!, sm) // sm 전달
}
}
return UserDto(sm.profile!!, sm.image!!)
}
class UserProfileRepository {
suspend fun findProfile(userId: Long, continuation: Continuation) { // 리턴값이 필요 없어짐
delay(100L)
continuation.resumeWith(Profile())
}
}
class UserImageRepository {
suspend fun findImage(profile: Profile, continuation: Continuation) { // 리턴값이 필요 없어짐
delay(100L)
continuation.resumeWith(Image())
}
}
resumeWith()
가 불리면서 내부에서 findUser
를 재귀적으로 호출하기에 label
에 따라 로직들이 순차적으로 실행될 것이다.Continuation
을 전달하는 것findUser
최초 호출
label
이 0인 sm : Continuation
생성findProfile
에 sm
전달하며 호출
Profile
을 생성 후 sm.resueWith
호출sm
내부에서 label
에 따라 알맞은 결과 데이터 저장Continuation
이 자신을 넘기며 다시 findUser
호출
sm : Continuation
이 null
이 아니므로 생성하지 않고 그대로 사용label
1인 sm
으로 로직 동작label
2가 되면 결과를 리턴suspend
함수만으로 구현했던 초기 코드를 디컴파일 해보면 우리가 만든 Continuation
코드와 유사하게 디컴파일된다.Continuation
을 전달하며label
로 각 상황을 매기고// 실제 Continuation 인터페이스이다.
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}