Композиция двух зависимых асинхронных этапов через CompletableFuture
Даны два асинхронных помощника: fetchUser(id) возвращает CompletableFuture<User>, а fetchOrders(user) возвращает CompletableFuture<List<Order>> (у каждого Order есть int amount()). Скомпонуйте их так, чтобы второй вызов начинался только после завершения первого, и верните суммарные траты пользователя.
Ограничения:
- оба вызова образуют неблокирующую цепочку — нигде нет
get()/join() - возвращаемый future должен быть плоским
CompletableFuture<Integer>, а не вложенным
CompletableFuture<Integer> totalSpent(String userId) {
// ваш код здесь
}
Допишите реализацию.
CompletableFuture — это Future, который можно завершить самому и выстроить в цепочку этапов. thenApply преобразует готовое значение обычной функцией; thenCompose присоединяет этап, сам возвращающий CompletableFuture, и разворачивает результат, поэтому зависимые асинхронные вызовы идут по порядку без блокирующего get().
- ✗Вызывать
get()между этапами, превращая асинхронную цепочку обратно в блокирующий код - ✗Применять
thenApply, когда функция возвращает future, получая вложенныйCompletableFuture - ✗Считать, что
CompletableFutureнельзя завершить вручную или скомпоновать
- →Когда вы возьмёте
thenComposeвместоthenApply? - →Чем
thenCombineотличается отthenComposeпри соединении двух future?
Решение
CompletableFuture<Integer> totalSpent(String userId) {
return fetchUser(userId) // CompletableFuture<User>
.thenCompose(user -> fetchOrders(user)) // разворачивает CF<List<Order>>
.thenApply(orders -> orders.stream() // преобразует готовое значение
.mapToInt(Order::amount)
.sum());
}
Идея. fetchOrders зависит от результата fetchUser, а сама возвращает CompletableFuture. Если применить здесь thenApply, получится вложенный CompletableFuture<CompletableFuture<List<Order>>>. thenCompose присоединяет зависимый этап и разворачивает его в один плоский future — это аналог flatMap для future. Финальный thenApply — чистое преобразование готового списка, поэтому нигде не нужен блокирующий get(): вся работа выполняется в колбэках, когда предыдущий этап завершится.