Java 如何在CompletableFutures中收集成功和错误?
我想将多个请求并行发送到Web服务。结果应该通过success+error收集,然后调用方可以进一步分析Java 如何在CompletableFutures中收集成功和错误?,java,completable-future,Java,Completable Future,我想将多个请求并行发送到Web服务。结果应该通过success+error收集,然后调用方可以进一步分析 public Map.Entry<Rsp, Errors> sendApiRequests(List<Req> reqs) { //will mostly remain null as errors won't occur frequently List<Rsp> errors = null; List<Completabl
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);
}
public Map.Entry sendapi请求(列表请求){
//由于错误不会频繁发生,因此大多数情况下将保持为空
列表错误=null;
上市期货=
需求流()
.map(请求->CompletableFuture.SupplySync(()->发送(请求))
.例外情况(ex->{
//TODO失败,因为列表应该是它的最终结果。
//但不希望实例化,因为大多数情况下将保持为空
if(errors==null)errors=new ArrayList();
错误。添加(req);
}))
.collect(Collectors.toList());
//并行发送api请求
列表响应=futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
//TODO如何收集错误?每个错误还应提供导致故障的底层Req。
//如果出现异常,则不应中止挂起的请求
返回新的SimpleEntry(响应、错误);
}
问题:我怎样才能收集所有响应,同时收集send()
方法期间抛出的异常
我的目标不是返回两个列表:一个包含所有成功的响应,另一个包含错误。虽然这是一个老问题,但我需要类似的东西,并且能够从上面的评论和其他不太正确的互联网片段中拼凑出一个可行的解决方案 方法和CompletableFutureCollector类将返回每个请求的响应或错误列表。这是在Java11中实现的,但应该适用于Java8。我建议对此进行调整,以传入
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
});
最终列表结果=
sendApiRequests(List.of(newreq());
结果.流()
.filter(CompletableFutureCollector.CollectorResult::hasError)
.map(CompletableFutureCollector.CollectorResult::getError)
.forEach(错误->{
//做一些有错误的事情
});
结果.流()
.filter(CompletableFutureCollector.CollectorResult::hasResult)
.map(CompletableFutureCollector.CollectorResult::getResult)
.forEach(rsp->{
//对回应做点什么
});
公共列表发送请求(列表请求){
//实际的发送实现可以是您希望异步执行的任何操作
返回CompletableFutureCollector.mapAsyncAndCollectResult(请求,请求->发送(请求));
}
// ...
私有最终静态类CompletableFutureCollector{
私有CompletableFutureCollector(){
}
公共静态收集器collectResult(){
返回Collectors.collectingAndThen(Collectors.toList(),joinResult());
}
私有静态函数joinResult(){
返回期货->全部(期货)
.thenApply(v->futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
private static您始终可以将成功的响应和错误包装到一个公共对象中,并返回这些对象的列表。如果您通过异常获得错误,则可以使用来处理每个阶段的两个流。您可能还希望从该方法返回一个CompletableFuture
,以便该方法在每个阶段都保持异步调用方。您可以在列表中使用allOf()
来执行此操作。好的,但是如何准确捕获异步进程的异常以及在何处捕获该异常?我的异常可能会在CompletableFuture::join
上抛出?但是我如何才能捕获一个进程的异常,而继续所有其他进程的异常?如果调用join()
,异常确实会被抛出(包装在CompletionException
中)因此,您确实可以用try/catch来包围这个调用,并在那里处理它。我并不清楚您最终期望的结果是什么,以及是什么阻止了您处理所有结果。@迪迪尔请查看我的编辑,希望能更清楚地说明这一点。
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);
}
}
}