ParallelJobsFuture implements JobFuture { @Override public CompletableFuture<Void> waitAsync() { Stream<CompletableFuture> futures = jobs .map(JobFuture::waitAsync) .map(curFuture -> curFuture.exceptionally(this::collectErrors)); return CompletableFuture .allOf(array(futures)) .thenApply(this::throwSummaryExceptionIfNeeded); } ParallelJobsFuture(JobFuture... jobs);  ParallelJobsFuture(List<JobFuture> jobs);  ParallelJobsFuture(List<JobInfo> jobInfoList, QueueClient queueClient); @Override void waitUntilComplete(); @Override void waitUntilComplete(Duration timeout); @Override CompletableFuture<Void> waitAsync();  }
@Test(timeOut = 3000L) public void testWaitAsync() throws Exception { new ParallelJobsFuture(successJobFutures(3)) .waitAsync() .join(); }
@Test public void testWaitAsync_failed() throws Exception { try { new ParallelJobsFuture(failedJobFutures(3)) .waitAsync() .join(); fail("Parallel future must throw exception"); } catch (CompletionException ex) { assert ex.getCause() instanceof JobFailedException; JobFailedException cause = (JobFailedException) ex.getCause(); assertEquals(cause.getSubExceptions().size(), 3); } }