[Java8] Chapter 6-5. CompletableFuture, 작업의 조합과 예외 처리

 

✍️ CompletableFuture, 작업의 조합

Future만으론 작업을 이어서 수행하는 것이 어려웠다.

= Callback이 없었기에 비동기적인 작업 두 개를 연결하는 것 자체가 어려웠다.

 

가령, hello가 끝나고 world를 해야 한다면 두 번의 get 호출이 이어져야 했다.

CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
	System.out.println("hello " + Thread.currentThread().getName());
	
	return "hello";
});

CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
	System.out.println("world! " + Thread.currentThread().getName());
	
	return "world!";
});

String res1 = hello.get();
String res2 = world.get();

System.out.println(res1 + res2);

// hello ForkJoinPool.commonPool-worker-1
// world! ForkJoinPool.commonPool-worker-2
// helloworld!

 

thenCompose

반면 CompletableFuture의 thenCompose를 사용하면 연관관계가 있는 비동기 작업을 이어서 수행할 수 있다.

thenCompose는 두 작업 간의 의존성이 필요할 때 사용된다. 예를 들어 A 작업이 수행된 다음 B 작업을 수행해야 하는 상황

CompletableFuture<String> helloworld = CompletableFuture.supplyAsync(() -> {
	System.out.println("hello " + Thread.currentThread().getName());

	return "hello";
}).thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "world"));

System.out.println(helloworld.get());

// hello ForkJoinPool.commonPool-worker-1
// helloworld

 

thenCombine

thenCombine은 연관관계가 없는 독립적인 작업을 조합할 때 사용된다. 

가령 관계가 없는 A, B 두 작업을 독립적으로 수행하고 양쪽의 결과가 완료됐을 때 결과물을 BiFunction에서 처리한다.

CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
	System.out.println("hello " + Thread.currentThread().getName());

	return "hello";
});

CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
	System.out.println("world! " + Thread.currentThread().getName());

	return "world!";
});

CompletableFuture<String> helloworld = hello.thenCombine(world, (h, w) -> h + w);

System.out.println(helloworld.get());

// hello ForkJoinPool.commonPool-worker-2
// world! ForkJoinPool.commonPool-worker-2
// helloworld!

 

allOf

작업이 두 개 이상일 때 모든 작업을 합쳐서 수행하는 방법으로 allOf 메서드가 있다.

allOf는 여러 작업을 합쳐서 수행한다. 그렇다면 A 작업의 결과는 String, B 작업의 결과는 void, C 작업의 결과는 Integer 일 수 있다. 그렇기에 allOf로 조합한 작업의 결과는 항상 void를 반환한다. 

 

만약 각 작업의 결과를 반환받고 싶다면 futures의 stream을 열어 결괏값을 List<Object>로 받을 수 있다.

CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
	return "hello";
});

CompletableFuture<Void> empty = CompletableFuture.runAsync(() -> {
});

CompletableFuture<Integer> three = CompletableFuture.supplyAsync(() -> {
	return 3;
});

CompletableFuture[] futuresArray = { hello, empty, three };
List<CompletableFuture> futures = Arrays.asList(futuresArray);

CompletableFuture<List<Object>> results = CompletableFuture.allOf(futuresArray).thenApply(v -> {
	return futures.stream()
			.map(o -> o.join()) // ignore unchecked exception
			.collect(Collectors.toList());
});

results.get().forEach(System.out::println);

// hello
// null
// 3

 

anyOf

anyOf는 여러 작업가운데 가장 먼저 끝난 작업의 결과를 반환한다.  

CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
	return "hello";
});

CompletableFuture<Integer> world = CompletableFuture.supplyAsync(() -> {
	return 3;
});

CompletableFuture<Object> future = CompletableFuture.anyOf(hello, world);

System.out.println(future.get());

// hello or 3

 

🍊 CompletableFuture, 예외 처리

exceptionally(Function)

비동기 작업에서 에러가 발생한다면 exceptionally에서 에러 타입을 받고 무언가를 반환할 수 있다. 

boolean throwError = true;

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
	if (throwError)
		throw new IllegalArgumentException();

	System.out.println("hello" + Thread.currentThread().getName());

	return "hello";
}).exceptionally(e -> {
	System.out.println(e);

	return "error!";
});

System.out.println(future.get());

// java.util.concurrent.CompletionException: java.lang.IllegalArgumentException
// error!

 

handle(BiFunction)

handle은 exceptionally보다 일반적으로 사용되는 메서드로 정상적으로 실행되는 상황과 에러가 발생했을 때의 상황 모두를 다룬다.

boolean throwError = true;

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
	if (throwError)
		throw new IllegalArgumentException();

	System.out.println("hello" + Thread.currentThread().getName());

	return "hello";
}).handle((result, ex) -> {
	if (ex != null) {
		System.out.println(ex);

		return "error!";
	}

	return result;
});

System.out.println(future.get());

// java.util.concurrent.CompletionException: java.lang.IllegalArgumentException
// error!