前のエントリでいい感じで書けそうみたいなことを書いたが、例外処理について言及していなかった。
CompletableFuture は Promise 同様エラー処理用にcompleteExceptionally(Throwable ex) というメソッドを持っている。これを呼ぶと後続の exceptionally() にエラー処理が移る (そしてリカバリできる) 。
では普通の、時間のかかり、なおかつ検査例外を出す同期メソッドを CompletableFuture として実行する場合例外はどうすればいいだろうか?
runAsync などの中では CompletableFuture インスタンスは見えないので、catch して明示的に completeExceptionally() することができない。
いくつか方法がありそうだけど、よくわかってない
CompletableFuture を明示的に扱うラッパメソッドを作る
package com.company; | |
import java.util.concurrent.CompletableFuture; | |
import java.util.function.Consumer; | |
public class Main { | |
public static void main(String[] args) { | |
new Main().doMain(args); | |
} | |
private static <T> CompletableFuture<T> async(Consumer<CompletableFuture<T>> function) { | |
final CompletableFuture<T> future = new CompletableFuture<>(); | |
function.accept(future); | |
return future; | |
} | |
public void doMain(String[] args) { | |
final CompletableFuture<Void> future1 = async(f -> { | |
try { | |
f.complete("Foobar"); | |
} catch (Exception e) { | |
f.completeExceptionally(e); | |
} | |
}).thenAccept(x -> { | |
System.out.println(x); | |
}); | |
final CompletableFuture<Void> future2 = async(f -> { | |
try { | |
throw new RuntimeException("exception"); | |
} catch (Exception e) { | |
f.completeExceptionally(e); | |
} | |
}).exceptionally(ex -> { | |
System.out.println("exception!!"); | |
return ex; | |
}).thenAccept(x -> { | |
System.out.println(x); | |
}); | |
CompletableFuture.allOf(future1, future2).thenRun(() -> { | |
System.out.println("All done"); | |
}); | |
} | |
} |
こういう感じで、JS でいうところの new Promise( (resolve, reject) => {}) に相当するメソッドを作って、明示的に同期コードでの終了時/例外時の処理を行う。
JS の流れだとわかりやすいけど、ラッパメソッドとかいちいち書きたくはないし、せっかくマルチスレッドによって同期的に書ける処理を非同期っぽくなおすのはダサい。
全部 RuntimeException にしてしまう
runAsync 内で全例外をキャッチして非検査例外にしてしまう。
この場合投げられた例外は java.util.concurrent.CompletionException (これも非検査例外) にさらにラップされて exceptionally に渡される。
全例外 catch して completeExceptionally するのも実質非検査への変換なのでこれでもよさそう。実質はラッパメソッドでキャッチして completeExceptionally するのと変わりない。
その他
検査例外をうまく生かしつつ処理するというのが難しそう。各ステージで検査例外は全て処理するというのが想定されているのだろうか? とりあえず全部 catch して uncheck にするというのをやっていると、当然検査例外をまともに処理しようという気にはならなくなる。
検索例外を投げるメソッドは、直接メソッド参照のスタイルで runAsync(this::FooBar) 等と渡せない。