我有一个Vert.x Web服务,该服务需要对外部API进行一系列分页调用。外部服务通过在每个响应中包括一个“下一个”字段(这是到下一页数据的直接链接)以及获取所有数据所需的总页数的计数来实现分页。这是一个示例响应:
"pagination": {
"count": 1000,
"totalPages": 112,
"next": "https://some-host.com?next=some-long-alphanumeric-hash"
},
"data": [ ... ]
进行第一个API调用后,我知道了后续调用的总数(在本示例中为111个)以及用于获取下一页数据的URL。在同步环境中,我可以做这样的事情:
Collection aggregatedResults;
int count = 0;
String nextUrl = "";
while (count <= total pages) {
make next request
add the chunk of data from this response to the collection
store the next URL in local variable
increment count
}
我使用Vertx的策略是使用Future
来表示单个调用的结果,然后将它们与CompositeFuture.all()
链接在一起。到目前为止,这大致就是我所拥有的(省略一些代码以节省空间):
private String nextUrl; // global String
doFirstCall(client).setHandler(async -> {
if (async.failed()) {
// blah
} else {
Response response = async.result();
int totalPages = response.getTotalPages();
next = response.getNext();
List<Future> paginatedFutures = IntStream
.range(0, totalPages - 1)
.mapToObj(i -> {
Promise<Response> promise = Promise.promise();
doIndividualPaginatedCall(client, next)
.setHandler(call -> {
if (call.succeeded()) {
Response chunk = call.result();
next = chunk.getNext(); // store the next URL in global string so it can be accessed within the loop
promise.complete(chunk);
} else {
promise.fail(call.cause());
}
});
return promise.future();
})
.collect(Collectors.toList());
CompositeFuture.all(paginatedFutures).setHandler(all -> {
if (all.succeeded()) {
// Do something with the aggregated responses
}
});
}
});
运行此代码时,第一个调用总是成功,并且我成功存储了“下一个” URL。然后,我随后进行的所有分页调用都指向从第一次调用中获得的相同URL,并且我看到这样的日志:
Call succeeded. i: 16, next: https://blah.com/blah?filter=next(DnF1ZXJ5VGhlbkZldGNoBQAAAAAAlMYVFjdaM2ducHBaVGJHeWV5ZjRzNGRQMXcAAAAAAJTGNhYzcWlRTDEyeVJZS05PeV84QkJlLTVnAAAAAACUxjYWa3UzUkx1MXZURG1Pc2E5WGt5RG9pdwAAAAAAlMY2FnY4TVhXajlqUmMtWEQwWU1naGZFN3cAAAAAAJTGVxZCWWFUV19XR1RXQ05DRkI0NGw4M0xB)
Call succeeded. i: 17, next: https://blah.com/blah?filter=next(DnF1ZXJ5VGhlbkZldGNoBQAAAAAAlMYVFjdaM2ducHBaVGJHeWV5ZjRzNGRQMXcAAAAAAJTGNhYzcWlRTDEyeVJZS05PeV84QkJlLTVnAAAAAACUxjYWa3UzUkx1MXZURG1Pc2E5WGt5RG9pdwAAAAAAlMY2FnY4TVhXajlqUmMtWEQwWU1naGZFN3cAAAAAAJTGVxZCWWFUV19XR1RXQ05DRkI0NGw4M0xB)
Call succeeded. i: 18, next: https://blah.com/blah?filter=next(DnF1ZXJ5VGhlbkZldGNoBQAAAAAAlMYVFjdaM2ducHBaVGJHeWV5ZjRzNGRQMXcAAAAAAJTGNhYzcWlRTDEyeVJZS05PeV84QkJlLTVnAAAAAACUxjYWa3UzUkx1MXZURG1Pc2E5WGt5RG9pdwAAAAAAlMY2FnY4TVhXajlqUmMtWEQwWU1naGZFN3cAAAAAAJTGVxZCWWFUV19XR1RXQ05DRkI0NGw4M0xB)
TLDR:如何执行一系列分页的API调用,其中URL在每个调用之间更改,并且在上一个调用完成执行之前未知。我尝试使用CompositeFuture.join
,但效果相同。我知道要进行顺序组合,应该使用compose()
,但是如何组合未知数量的函数调用?
答案 0 :(得分:1)
您正在尝试对next
进行突变
if (call.succeeded()) {
Response chunk = call.result();
next = chunk.getNext(); // store the next URL in global string so it can be accessed within the loop
promise.complete(chunk);
}
但是实际上您正在重用第一次使用的相同值:
next = response.getNext();
这是因为您的所有调用都被调用了很长时间,甚至其中一个都没有返回。
由于在上一次调用返回之前无法知道next
的值,因此您必须以递归方式实现它,并删除map
:
doIndividualPaginatedCall(client, next)
.setHandler(call -> {
if (call.succeeded()) {
Response chunk = call.result();
next = chunk.getNext(); // store the next URL in global string so it can be accessed within the loop
promise.complete(chunk);
doIndividualPaginatedCall(client, next);
} else {
promise.fail(call.cause());
}
});
请注意,我实际上并未编译您的代码,因此可能需要进行更多更改才能使其真正起作用。
答案 1 :(得分:0)
在这个问题中,我误解了我要连接的API,并且“下一个”字段在两次调用之间不会更改。因此,这个问题简化为“如何在Vertx中实现异步客户端分页,在每个分页调用之前我 都知道URL?”。我接受Alexey的回答是因为它回答了原始问题,并张贴了我在下面使用的粗略代码,以防万一使用相同用例的人:
// start()
doFirstCall(client).setHandler(async -> {
if (async.succeeded()) {
Response response = async.result();
final int totalPages = response.totalPages();
final String next = response.next();
// Fire off 'totalPages' async calls and wait for them to come back
List<Future> paginatedFutures = IntStream
.range(0, totalPages)
.mapToObj(i -> {
Promise<Response> promise = Promise.promise();
doPaginatedCall(client).setHandler(call -> {
if (call.succeeded()) {
promise.complete(call.result());
}
});
return promise.future();
}).collect(Collectors.toList());
// Wait for all HTTP calls to come back before continuing
CompositeFuture.join(paginatedFutures).setHandler(all -> {
if (all.succeeded()) {
// Do something with all of the aggregated calls
}
});
}
});
private Future<Response> doFirstCall(WebClient client) {
Promise<Response> promise = Promise.promise();
// If call succeeded, promise.complete(response), otherwise fail
return promise.future();
}
private Future<Response> doPaginatedCall(WebClient client, String nextUrl) {
Promise<Response> promise = Promise.promise();
// If call succeeded, promise.complete(response), otherwise fail
return promise.future();
}