Сначала ответь вслух
Скажи ответ вслух хотя бы пару минут, потом открой эталон и сравни. Эталон открыт всем и не требует входа.
Показать ответ
Эталонный ответ
thenApply(fn) применяет обычную функцию к результату. Если функция сама возвращает CompletableFuture, получится вложенный CompletableFuture<CompletableFuture<T>>. thenCompose(fn) ждёт функцию, которая возвращает CompletionStage, и разворачивает его.
CompletableFuture<User> user = users.findAsync(id);
CompletableFuture<String> name = user.thenApply(User::name);
CompletableFuture<List<Order>> orders = user.thenCompose(u -> orders.findAsync(u.id()));Варианты без Async выполняются в потоке, который завершил предыдущий этап, или в вызывающем потоке, если этап уже завершён. Варианты с Async без executor уходят в ForkJoinPool.commonPool().
Как у тебя получилось?
Уточняющие вопросы
Так обычно копают глубже. Раскрой строку, когда ответишь вслух.
Уточнение 1-го уровня: Почему опасно отдавать блокирующие вызовы в supplyAsync без своего executor?
Ответ уровня Middle
Общий пул рассчитан на число ядер. Блокирующие задачи займут его потоки, и встанут остальные задачи пула, включая параллельные стримы.
Уточнение 2-го уровня: Как обработать ошибку в цепочке?
Ответ уровня Middle
exceptionally подставит значение, handle получит и результат, и исключение, whenComplete только наблюдает. Исключение внутри цепочки приходит обёрнутым в CompletionException.
Типичные ошибки
- Вызывать join() внутри цепочки вместо thenCompose.
- Запускать блокирующий ввод-вывод в общем пуле.
Первоисточники
Быстрые задания по теме
Для темы «CompletableFuture» быстрых заданий пока нет
Можно проверить всю зону «Многопоточность» тестом на 10 минут.
Проверить себя за 10 минут