簡體   English   中英

如何在 CompletableFutures 中收集成功和錯誤?

[英]How to collect both success and error within CompletableFutures?

我想向 Web 服務並行發送多個請求。 結果應該通過成功+錯誤來收集,然后可以由調用者進一步分析。

public Map.Entry<Rsp, Errors> sendApiRequests(List<Req> reqs) {
    //will mostly remain null as errors won't occur frequently
    List<Rsp> errors = null;

    List<CompletableFuture<Rsp>> futures =
            reqs.stream()
                .map(req -> CompletableFuture.supplyAsync(() -> send(req))
                        .exceptionally(ex -> {
                            //TODO this fails, because list should be final for it.
                            //but don't want to instantiate as mostly will remain just null
                            if (errors == null) errors = new ArrayList<>();
                            errors.add(req);
                        }))
                .collect(Collectors.toList());

    //send api requests in parallel
    List<Rsp> responses = futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList());

    //TODO how to collect the errors? each error should also provide the underlying Req that caused the failure.
    //pending requests should not be aborted if any throwns an exception
    return new SimpleEntry(responses, errors);
}

問題:我如何打賭收集所有響應,同時收集send()方法期間拋出的異常?

我的目標不是返回兩個列表:一個包含所有成功的響應,一個包含錯誤。

雖然這是一個老問題,但我需要類似的東西,並且能夠從上面的評論和其他不太正確的互聯網片段中拼湊出一個可行的解決方案。

該方法和CompletableFutureCollector類將為每個請求返回響應或錯誤列表。 這是在 Java 11 中實現的,但應該適用於 Java 8。我建議調整它以傳入java.util.concurrent.Executor來控制並行度。 例如,您可以像這樣使用它:

    final List<CompletableFutureCollector.CollectorResult<Rsp>> results =
            sendApiRequests(List.of(new Req()));
    results.stream()
            .filter(CompletableFutureCollector.CollectorResult::hasError)
            .map(CompletableFutureCollector.CollectorResult::getError)
            .forEach(error -> {
                // Do something with errors
            });
    results.stream()
            .filter(CompletableFutureCollector.CollectorResult::hasResult)
            .map(CompletableFutureCollector.CollectorResult::getResult)
            .forEach(rsp -> {
                // Do something with responses
            });
    public List<CompletableFutureCollector.CollectorResult<Rsp>> sendApiRequests(List<Req> reqs) {
        // The actual send implementation could be anything that you'd like to do asynchronously  
        return CompletableFutureCollector.mapAsyncAndCollectResult(reqs, req -> send(req));
    }

    // ...

    private final static class CompletableFutureCollector {
        private CompletableFutureCollector() {
        }

        public static <X, T extends CompletableFuture<X>> Collector<T, ?, CompletableFuture<List<X>>> collectResult() {
            return Collectors.collectingAndThen(Collectors.toList(), joinResult());
        }

        private static <X, T extends CompletableFuture<X>> Function<List<T>, CompletableFuture<List<X>>> joinResult() {
            return futures -> allOf(futures)
                    .thenApply(v -> futures.stream()
                            .map(CompletableFuture::join)
                            .collect(Collectors.toList()));
        }

        private static <T extends CompletableFuture<?>> CompletableFuture<Void> allOf(final List<T> futures) {
            return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        }

        public static <T, R> List<CompletableFutureCollector.CollectorResult<R>> mapAsyncAndCollectResult(
                final Collection<T> items, final Function<T, R> action) {
            return items.parallelStream()
                    .map(task -> CompletableFuture.supplyAsync(() -> action.apply(task)))
                    .map(CompletableFutureCollector.CollectorResult::handle)
                    .collect(CompletableFutureCollector.collectResult())
                    .join();
        }

        private final static class CollectorResult<R> {
            private final R result;
            private final Throwable error;

            private CollectorResult(final R result, final Throwable error) {
                this.result = result;
                this.error = error;
            }

            public boolean hasError() {
                return getError() != null;
            }

            public boolean hasResult() {
                return !hasError();
            }

            public R getResult() {
                return result;
            }

            public Throwable getError() {
                return error instanceof CompletionException ? error.getCause() : error;
            }

            public static <R> CompletableFuture<CompletableFutureCollector.CollectorResult<R>> handle(final CompletableFuture<R> future) {
                return future.handle(CompletableFutureCollector.CollectorResult::new);
            }
        }
    }

注意:我尚未完全測試此解決方案,因為它改編自具有一些額外快速故障取消邏輯的工作實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM