sirius.kernel.async.Promises Maven / Gradle / Ivy
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.async;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Provides utility functions when dealing with {@link Promise promises}.
*/
public class Promises {
private Promises() {
}
/**
* Iterates over the given input and invokes an async function for each element.
*
* Note that this is done sequentially, therefore only one call happens at a time
*
* @param items the items to iterate over
* @param toPromiseHandler the async function which returns a promise to indicate completion
* @param resultConsumer the handler used to collect the results
* @param the input type
* @param the output type generated by the async function
* @return a future which is fulfilled once all items have been processer or failed if one item fails
*/
public static Future processChain(Iterable items,
Function> toPromiseHandler,
BiConsumer resultConsumer) {
Future result = new Future();
processChain(items.iterator(), toPromiseHandler, resultConsumer, result);
return result;
}
private static void processChain(Iterator iter,
Function> toPromiseHandler,
BiConsumer resultConsumer,
Future completionFuture) {
if (!iter.hasNext()) {
completionFuture.success();
return;
}
I item = iter.next();
toPromiseHandler.apply(item).onSuccess(result -> {
resultConsumer.accept(item, result);
processChain(iter, toPromiseHandler, resultConsumer, completionFuture);
}).onFailure(completionFuture::fail);
}
/**
* Transforms a collection of items into a promise for a list of results while invoking an async function for
* each item.
*
* @param input the items to iterate over
* @param toPromiseHandler the async function which returns a promise to indicate completion
* @param the input type
* @param the output type generated by the async function
* @return a promise containing the invocation results of the async function for each item in the input
*/
public static Promise> sequence(Iterable input, Function> toPromiseHandler) {
Promise> result = new Promise<>();
List buffer = new ArrayList<>();
processChain(input, toPromiseHandler, (ignored, output) -> buffer.add(output)).onSuccess(() -> result.success(
buffer)).onFailure(result::fail);
return result;
}
/**
* Turns a list of promises into a promise for a list of values.
*
* Note that all values need to have the same type.
*
* If only the completion of all promises matters in contrast to their actual result, a {@link Barrier} can also
* be used. This also permits to wait for promises of different types.
*
* @param list the list of promises to convert.
* @param the type of each promise.
* @return the promise which will complete if all promises completed or if at least on failed.
*/
public static Promise> parallel(List> list) {
final Promise> result = new Promise<>();
// Create a list with the correct length
final List resultList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
resultList.add(null);
}
// Keep track when we're finished
final CountDownLatch latch = new CountDownLatch(list.size());
// Iterate over all promises and create a completion handler, which either forwards a failure or which placesy
// a successfully computed in the created result list
int index = 0;
for (Promise promise : list) {
final int currentIndex = index;
promise.onComplete(new CompletionHandler() {
@Override
public void onSuccess(@Nullable V value) throws Exception {
if (!result.isFailed()) {
// onSuccess can be called from any thread -> sync on resultList...
synchronized (resultList) {
resultList.set(currentIndex, value);
}
// Keep track how many results we're waiting for and forward the result when we're finished.
latch.countDown();
if (latch.getCount() <= 0) {
result.success(resultList);
}
}
}
@Override
public void onFailure(Throwable throwable) throws Exception {
result.fail(throwable);
}
});
index++;
}
return result;
}
}