Асинхронный CompletableFuture. Часть 2
У CompletableFuture есть ещё интересные функции. Например, надо построить цепочку из асинхронных вызовов. Т.е. после завершения первой асинхронной функции запустить вторую, после второй третью и т.д. В JavaScript для этого применяются promise. В Java можно использовать CompletableFuture.
Выглядит это так:
private void thenApply() throws ExecutionException, InterruptedException { final CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> { System.out.println("job started"); sleep(3); System.out.println("job done"); return "feature done|"; }).thenApply(result -> { System.out.println("applay result:" + result); return result + " applied"; }); System.out.println("waiting..."); String result = future.get(); System.out.println("finished, result:" + result); }
Сначала выполняется первая задача (sleep(3)) и только после её завершения запустится вторая задача. Причём второй задаче в качестве входного параметра можно передать результаты первой задачи.
А что делать с ошибками? Как их обрабатывать? У CompletableFuture есть встроенный механизм для обработки ошибок, применить его можно так:
private void thenApplyException() throws ExecutionException, InterruptedException { final CompletableFuture<String> future = CompletableFuture.supplyAsync(()-> { System.out.println("job started"); sleep(3); throw new RuntimeException("runTime exception"); }).thenApply(result -> { System.out.println("applay result:" + result); return result + " applied"; }).exceptionally(exception -> { System.out.println("got exception, err:" + exception.getMessage()); return exception.getMessage(); }); System.out.println("waiting..."); String result = future.get(); System.out.println("finished, result:" + result); }
(*) Обратите внимание на блок exceptionally, он запустится, если одна из задач выбросит исключение.
У CompletableFuture есть ещё интересная возможность: запускать несколько асинхронных задач, подождать, когда они завершатся и обработать полученные результаты.
Вот пример:
private void acceptBoth() { final CompletableFuture<String> futureOne = CompletableFuture.supplyAsync(()-> { System.out.println("job one started"); sleep(3); System.out.println("job one is done"); return "feature done one"; }); final CompletableFuture<String> futureTwo = CompletableFuture.supplyAsync(()-> { System.out.println("job two started"); sleep(7); System.out.println("job two is done"); return "feature done two"; }); System.out.println("waiting..."); futureOne.thenAcceptBothAsync(futureTwo, (result1, result2) -> System.out.println("join:" + result1 + " " + result2)); System.out.println("end"); }
Обратите внимание на структуру futureOne.thenAcceptBothAsync. Ждём, когда завершатся обе задачи, и обрабатываем итоговый результат. В отличие от предыдущих примеров, в этом фрагменте кода поток выполнения программы не ждёт, когда завершатся future, а идёт дальше.
Вывод:
CompletableFuture из пакета java.util.concurrent предоставляет полезный и простой в использовании функционал, который помогает ускорить разработку и упростить код. В то же время надо помнить, что в java.util.concurrent много тонких моментов, требующих понимания и некоторой сноровки в использовании.
Есть вопрос? Напишите в комментариях!