CompletionStage — 비동기 계산의 단계

CompletionStage — 비동기 계산의 단계

CompletionStage<T>는 **비동기 계산의 한 단계(stage)**예요. 다른 CompletionStage가 완료되면 액션을 수행하거나 값을 계산해요. 한 단계가 완료되면 다시 다른 의존 단계들을 촉발할 수 있어, 파이프라인처럼 연산을 이어 붙일 수 있어요. 대표 구현체는 CompletableFuture예요.

출처: Java API Reference

본문

기본 형태

이 인터페이스가 정의하는 기능은 몇 가지 기본 형태로, 다양한 사용 스타일을 담기 위한 더 큰 메서드 집합으로 확장돼요. 단계가 수행하는 계산은 인자와 결과를 요구하는지에 따라 Function(apply), Consumer(accept), Runnable(run)으로 표현돼요:

stage.thenApply(x -> square(x))
     .thenAccept(x -> System.out.print(x))
     .thenRun(() -> System.out.println());

추가 형태인 compose는 완료 스테이지를 반환하는 함수들로 계산 파이프라인을 구성할 수 있게 해줘요. 스테이지 계산에 넘어가는 인자는 촉발 단계(tiggering stage) 계산의 결과예요.

트리거 방식

한 단계의 실행은 단일 단계, 두 단계 모두, 또는 두 단계 중 하나의 완료로 촉발될 수 있어요.

  • 단일 단계 의존: then 접두사 메서드
  • 두 단계 모두 완료: 결과나 효과를 결합(thenCombine, thenAcceptBoth, runAfterBoth)
  • 두 단계 중 하나 완료: 어느 쪽 결과/효과가 쓰일지 보장하지 않음(applyToEither, acceptEither, runAfterEither)

실행 방식

단계 간 의존은 계산의 촉발을 제어하지만 특정 순서를 보장하진 않아요. 새 단계의 계산은 세 가지 방식으로 배치될 수 있어요:

  1. 기본 실행(default)
  2. 기본 비동기 실행(async 접미사 — 단계의 기본 비동기 실행 수단 사용)
  3. 커스텀(제공된 Executor 사용)

Executor 인자를 명시하는 메서드는 임의의 실행 속성을 가질 수 있고 동시 실행을 지원하지 않을 수도 있어요.

예외 처리

두 메서드 형태(handle, whenComplete)는 촉발 단계가 정상이든 예외적이든 무조건 계산을 지원해요. exceptionally는 촉발 단계가 예외적으로 완료될 때만 대체 결과를 계산하며, 자바 catch 키워드와 비슷해요.

그 외의 경우, 단계의 계산이 (비검사) 예외나 오류로 갑자기 끝나면, 그 완료를 요구하는 모든 의존 단계도 예외적으로 완료되고 그 예외를 cause로 가진 CompletionException이 전달돼요. 메서드 handle은 촉발 단계의 결과와 예외를 모두 받아 임의의 결과를 계산하는 가장 일반적인 연속 단계 생성 방법이에요. 두 메서드 모두 아래처럼 구조화된 계산을 가져야 해요:

function stage.complete -> (result, exception) -> {
    if (exception == null) {
        // triggering stage completed normally
    } else {
        // triggering stage completed exceptionally
    }
}

참고: 완료 결과를 전달하는 데 쓰는 인자(타입 T 파라미터)는 null일 수 있지만, 다른 어떤 파라미터에 null을 넘기면 NullPointerException이 던져져요.

이 인터페이스는 단계를 처음 만들거나 강제 완료, 완료 상태 조회, 완료 대기를 위한 메서드는 정의하지 않아요. toCompletableFuture()가 공통 변환 타입을 제공해 서로 다른 구현 간 상호운용을 가능하게 해요.

then 계열 (단일 단계 의존)

<U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn) — 이 단계가 정상 완료되면, 그 결과를 인자로 주어진 함수를 실행해 반환하는 새 CompletionStage를 반환해요. Optional.map/Stream.map과 유사해요. thenApplyAsync(fn)·thenApplyAsync(fn, executor) 변형이 있어요.

CompletionStage<Void> thenAccept(Consumer<? super T> action) — 정상 완료되면 결과를 인자로 주어진 액션을 실행해요. thenAcceptAsync(action)·thenAcceptAsync(action, executor) 변형이 있어요.

CompletionStage<Void> thenRun(Runnable action) — 정상 완료되면 주어진 액션을 실행해요(결과 사용 없음). thenRunAsync(action)·thenRunAsync(action, executor) 변형이 있어요.

thenCompose (파이프라인 구성)

<U> CompletionStage<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn) — 이 단계가 정상 완료되면 함수를 호출해 다른 CompletionStage를 얻고, 그 단계가 완료되면 같은 값으로 완료되는 새 단계를 반환해요. Optional.flatMap/Stream.flatMap과 유사해요. 진행을 보장하려면 함수가 결과의 최종 완료를 조정해야 해요. thenComposeAsync(fn)·thenComposeAsync(fn, executor) 변형이 있어요.

thenCombine / thenAcceptBoth / runAfterBoth (두 단계 모두)

<U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) — 이 단계와 other가 모두 정상 완료되면 두 결과를 인자로 함수를 실행해요. thenCombineAsync 변형이 있어요.

<U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action) — 두 단계가 모두 완료되면 두 결과를 인자로 액션을 실행해요.

CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action) — 두 단계가 모두 완료되면 액션을 실행해요(결과 사용 없음).

applyToEither / acceptEither / runAfterEither (두 단계 중 하나)

<U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn) — 이 단계나 other 중 하나가 정상 완료되면, 해당하는 결과를 인자로 함수를 실행해요.

CompletionStage<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action) — 하나가 완료되면 해당 결과를 인자로 액션을 실행해요.

CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action) — 하나가 완료되면 액션을 실행해요.

handle / whenComplete (무조건 계산)

<U> CompletionStage<U> handle(BiFunction<? super T,Throwable,? extends U> fn) — 이 단계가 정상이든 예외적으로든 완료되면, 단계의 결과(null일 수 있음)와 예외(null일 수 있음)를 인자로 함수를 실행하고 함수의 결과로 반환 단계를 완료해요. handleAsync 변형이 있어요.

CompletionStage<T> whenComplete(BiConsumer<? super T,? super Throwable> action) — 이 단계와 같은 결과/예외를 가진 새 단계를 반환하되, 이 단계가 완료될 때 주어진 액션을 실행해요. handle과 달리 완료 결과를 변환하도록 설계되지 않았으므로 액션은 예외를 던지면 안 돼요.

exceptionally 계열 (예외 전용)

CompletionStage<T> exceptionally(Function<Throwable,? extends T> fn) — 이 단계가 예외적으로 완료되면, 예외를 인자로 함수를 실행해 반환하는 새 단계를 반환해요. 정상 완료되면 같은 값으로 정상 완료돼요. 자바 catch와 비슷해요.

CompletionStage<T> exceptionallyAsync(Function<Throwable,? extends T> fn) — 기본 비동기 실행 수단으로 예외 처리 함수를 실행해요. exceptionallyAsync(fn, executor) 변형이 있어요. (JDK 12+)

CompletionStage<T> exceptionallyCompose(Function<Throwable,? extends CompletionStage<T>> fn) — 예외적으로 완료되면 예외에 함수를 적용한 결과로 새 단계를 구성(compose) 해요. exceptionallyComposeAsync(fn)·exceptionallyComposeAsync(fn, executor) 변형이 있어요. (JDK 12+)

변환

CompletableFuture<T> toCompletableFuture() — 이 단계와 같은 완료 속성을 유지하는 CompletableFuture를 반환해요. 이미 CompletableFuture면 자기 자신을 반환할 수 있어요.

더 알아보기 (Learn more)