Skip to content

Commit 0ff4424

Browse files
committed
make list completion not sequential, but fully async
1 parent 086957c commit 0ff4424

File tree

3 files changed

+76
-3
lines changed

3 files changed

+76
-3
lines changed

src/main/java/graphql/execution/Async.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Iterator;
88
import java.util.List;
99
import java.util.concurrent.CompletableFuture;
10+
import java.util.function.BiFunction;
1011

1112
@Internal
1213
public class Async {
@@ -34,6 +35,23 @@ public static <U> CompletableFuture<List<U>> each(List<CompletableFuture<U>> fut
3435
return overallResult;
3536
}
3637

38+
public static <T, U> CompletableFuture<List<U>> each(Iterable<T> list, BiFunction<T, Integer, CompletableFuture<U>> cfFactory) {
39+
List<CompletableFuture<U>> futures = new ArrayList<>();
40+
int index = 0;
41+
for (T t : list) {
42+
CompletableFuture<U> cf;
43+
try {
44+
cf = cfFactory.apply(t, index++);
45+
Assert.assertNotNull(cf, "cfFactory must return a non null value");
46+
} catch (Exception e) {
47+
cf = new CompletableFuture<>();
48+
cf.completeExceptionally(e);
49+
}
50+
futures.add(cf);
51+
}
52+
return each(futures);
53+
54+
}
3755

3856
public static <T, U> CompletableFuture<List<U>> eachSequentially(Iterable<T> list, CFFactory<T, U> cfFactory) {
3957
CompletableFuture<List<U>> result = new CompletableFuture<>();

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ protected CompletableFuture<ExecutionResult> completeValueForList(ExecutionConte
510510
ExecutionTypeInfo typeInfo = parameters.typeInfo();
511511
GraphQLList fieldType = typeInfo.castType(GraphQLList.class);
512512

513-
CompletableFuture<List<ExecutionResult>> resultsFuture = Async.eachSequentially(iterableValues, (item, index, prevResults) -> {
513+
CompletableFuture<List<ExecutionResult>> resultsFuture = Async.each(iterableValues, (item, index) -> {
514514

515515
ExecutionPath indexedPath = parameters.path().segment(index);
516516

src/test/groovy/graphql/execution/AsyncTest.groovy

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package graphql.execution
33
import spock.lang.Specification
44

55
import java.util.concurrent.CompletableFuture
6+
import java.util.concurrent.CompletionException
7+
import java.util.function.BiFunction
8+
9+
import static java.util.concurrent.CompletableFuture.completedFuture
610

711
class AsyncTest extends Specification {
812

@@ -47,7 +51,7 @@ class AsyncTest extends Specification {
4751
given:
4852
def input = ['a', 'b', 'c']
4953
def cfFactory = Mock(Async.CFFactory)
50-
cfFactory.apply('a', 0, _) >> CompletableFuture.completedFuture("x")
54+
cfFactory.apply('a', 0, _) >> completedFuture("x")
5155
cfFactory.apply('b', 1, _) >> {
5256
def cf = new CompletableFuture<>()
5357
cf.completeExceptionally(new RuntimeException("some error"))
@@ -71,7 +75,7 @@ class AsyncTest extends Specification {
7175
given:
7276
def input = ['a', 'b', 'c']
7377
def cfFactory = Mock(Async.CFFactory)
74-
cfFactory.apply('a', 0, _) >> CompletableFuture.completedFuture("x")
78+
cfFactory.apply('a', 0, _) >> completedFuture("x")
7579
cfFactory.apply('b', 1, _) >> { throw new RuntimeException("some error") }
7680

7781
when:
@@ -87,4 +91,55 @@ class AsyncTest extends Specification {
8791
exception.getMessage() == "some error"
8892
}
8993

94+
def "each works for mapping function"() {
95+
given:
96+
def input = ['a', 'b', 'c']
97+
def cfFactory = Mock(BiFunction)
98+
cfFactory.apply('a', 0) >> completedFuture('x')
99+
cfFactory.apply('b', 1) >> completedFuture('y')
100+
cfFactory.apply('c', 2) >> completedFuture('z')
101+
102+
103+
when:
104+
def result = Async.each(input, cfFactory)
105+
106+
then:
107+
result.isDone()
108+
result.get() == ['x', 'y', 'z']
109+
}
110+
111+
def "each with mapping function propagates factory exception"() {
112+
given:
113+
def input = ['a', 'b', 'c']
114+
def cfFactory = Mock(BiFunction)
115+
116+
117+
when:
118+
def result = Async.each(input, cfFactory)
119+
120+
then:
121+
1 * cfFactory.apply('a', 0) >> completedFuture('x')
122+
1 * cfFactory.apply('b', 1) >> { throw new RuntimeException('some error') }
123+
1 * cfFactory.apply('c', 2) >> completedFuture('z')
124+
result.isCompletedExceptionally()
125+
Throwable exception
126+
result.exceptionally({ e ->
127+
exception = e
128+
})
129+
exception instanceof CompletionException
130+
exception.getCause().getMessage() == "some error"
131+
}
132+
133+
def "each works for list of futures "() {
134+
given:
135+
completedFuture('x')
136+
137+
when:
138+
def result = Async.each([completedFuture('x'), completedFuture('y'), completedFuture('z')])
139+
140+
then:
141+
result.isDone()
142+
result.get() == ['x', 'y', 'z']
143+
}
144+
90145
}

0 commit comments

Comments
 (0)