프로그래밍/Kotlin

[Kotlin] CompletableDeferred의 개념과 활용

Lou Park 2022. 11. 12. 12:08

Promise가 그리울때

이벤트 기반으로 통신하는 프로토콜을 구현중에, JS의 Promise나 Dart의 Completer와 비슷한 역할을 하는 Kotlin 친구가 없을까하다 찾게되었다. 완벽한 대체재다!

 

CompletableDeferredpublic function을 이용해 완료하거나 취소할 수 있는 Deferred이다.

이를 활용하면 내가 원하는 시점까지 기다렸다가 값을 받을 수 있다. 그럼 SuspendableCoroutine이랑 뭐가 다르냐? 라는 생각이 들 수 있다. SuspentableCoroutine은 해당 블록 안에서만 complete/cancel을 제어할 수 있지만, CompletableDeferred는 그런 제약이 없다.

 

CompletableDeferred : Functions

abstract fun complete(value: T): Boolean

주어진 값으로 Deferred를 완료시킨다. Deferred가 완료되면 true를 반환한다.

만약 이미 완료되었다면 (혹은 취소되어버리든) false를 반환한다.

 

abstract fun completeExceptionally(exception: Throwable): Boolean

주어진 예외를 던져서 Deferred를 예외적으로 완료시킨다. complete와 마찬가지로,

Deferred가 완료되면 true를, 다른 곳에서 이미 완료되었다면 false를 반환한다.

 

 

CompletableDeferred 활용 예제

이 부분은 CompletableDeferred를 사용해서 만든 request 함수인데, 서버에 데이터를 요청하는 함수다.

private val rq = mutableMapOf<Int, (String?, Any?) -> Any?>()

suspend fun<T : Any> request(name: String, data: T): T {
    nextRid += 1 % 0x10000
    val rid = nextRid
    try {
        val deferred = CompletableDeferred<T>()
        rq[rid] = { error, data ->
            if (error != null) {
                deferred.completeExceptionally(Exception(error))
            }
            deferred.complete(data as T)
        }
        send(arrayOf(EVENT_TYPE_REQUEST, rid, name, data))
        return deferred.await()
    } finally {
        rq.remove(rid)
    }
}


private fun onRequest(eventFrame: Array<*>) {
    val rid = eventFrame[1] as Int
    val error = eventFrame[2] as? String
    val data = eventFrame[3]

    val callback = rq[rid]
    callback?.invoke(error, data)
}

request()

함수를 호출하면 rq에 콜백을 등록하고 빠르게 send까지 처리된다. 그리고는 onRequest()가 호출될때까지 반환하지 못할거다.

 

onRequest()

응답이오면 rq에 등록한 콜백을 invoke시킨다. 그러면 request의 CompletableDeferred가 완료될 것이고, 그에 상응하는 결과값과함께 request() 함수가 완료된다.

 

직접 사용하기는 더욱 간편해진다.

fun main(args: Array<String>) = runBlocking {
	val user = ev.request("login", Date())
}

 

언제 쓰면되나요?

아래는 Dart Completer의 예시 코드인데, 콜백기반의 DB API를 Future로 제어할 수 있게끔 바꿨다.

이렇듯, CompletableDeferred도 여러가지 사용 예들이 있겠지만 복잡한 비동기 코드나, 콜백기반 API를 심플한 coroutine 방식으로 사용하고자 할때이용하면 될 것같다.

 

// 출처: https://medium.com/@jessicajimantoro/completer-in-dart-flutter-e15f5739e96d
Future<Result> querySomething(String query) {
  final Completer<Result> completer = Completer<Result>();

  db.query(query, (result) {
    completer.complete(result);
  }, (error) {
    completer.completeError(error);
  });

  return completer.future; 
}