27

Today I experimented with the "new" CompletableFuture from Java 8 and found myself confused when I didn't find a runAsync(Callable) method. I can do it myself like shown below, but why is this (to me very obvious and useful utility method) missing? Am I missing something?

public static <T> CompletableFuture<T> asFuture(Callable<? extends T> callable, Executor executor) {
    CompletableFuture<T> future = new CompletableFuture<>();
    executor.execute(() -> {
        try {
            future.complete(callable.call());
        } catch (Throwable t) {
            future.completeExceptionally(t);
        }
    });
    return future;
}
diesieben07
  • 1,487
  • 1
  • 14
  • 25
  • Yes; `CompletableFuture` is sadly _never_ used anywhere in the JDK. http://www.nurkiewicz.com/2013/05/java-8-completablefuture-in-action.html – SLaks May 31 '15 at 16:26
  • 2
    @SLaks - yes, the *interface* is hideous. it's probably intended as a good implementation that we can use internally. very unfortunate that java does not have a good official async interface. – ZhongYu May 31 '15 at 17:12

2 Answers2

14

You are supposed to use supplyAsync(Supplier<U>)

In general, lambdas and checked exceptions do not work very well together, and CompletableFuture avoids checked exceptions by design. Though in your case it should be no problem.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • 7
    That just makes it seem even more inconsistent to me. Because yes, it _does_ allow you to handle exceptions using e.g. `whenComplete`. But you can only get the exceptions "into" the process by breaking out of the fluent style and using your own static helper methods which then call `completeExceptionally`. Which is really ugly to read in code. – diesieben07 May 31 '15 at 17:32
  • 7
    There were a *lot* of discussions about this on the mailing list. In the end, I wasn't convinced by Doug Lea, and I find the whole thing not programmer-friendly. I ended up making my own [Async](http://bayou.io/release/0.9/javadoc/bayou/async/Async.html) interface. – ZhongYu May 31 '15 at 17:35
  • 1
    Yeah, I saw that. Really good job on the API there, this is exactly what I would have expected CompletableFuture to be like. – diesieben07 May 31 '15 at 17:36
  • 1
    but there needs to be an *official* API, so that different libraries can interact. sigh. – ZhongYu May 31 '15 at 17:37
2

For those who need the functionality mentioned in the question: I have improved it a bit in order to ideally reflect the behavior of the existing CompletableFuture#completeAsync and CompletableFuture.supplyAsync functions.

// Java 9+ (Java 8 version below)
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

/**
 * {@link CompletableFuture} utils.
 *
 * @author stonar96
 *
 * @see CompletableFuture
 */
public final class CompletableFutureUtils {
    /**
     * Delegates the given Callable to
     * {@link CompletableFuture#completeAsync(Supplier)} using a new
     * CompletableFuture and handles checked exceptions accordingly to unchecked
     * exceptions.
     *
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param          <U> the function's return type
     * @return the new CompletableFuture
     * @see CompletableFuture#completeAsync(Supplier)
     */
    public static <U> CompletableFuture<U> callAsync(Callable<? extends U> callable) {
        return completeAsync(new CompletableFuture<>(), callable);
    }

    /**
     * Delegates the given Callable to
     * {@link CompletableFuture#completeAsync(Supplier)} using the given
     * CompletableFuture and handles checked exceptions accordingly to unchecked
     * exceptions.
     *
     * @param result   the CompletableFuture to be used
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param          <T> the function's return type
     * @return the given CompletableFuture
     * @see CompletableFuture#completeAsync(Supplier)
     */
    public static <T> CompletableFuture<T> completeAsync(CompletableFuture<T> result, Callable<? extends T> callable) {
        return result.completeAsync(callable == null ? null : () -> {
            try {
                return callable.call();
            } catch (Error e) {
                throw e;
            } catch (RuntimeException e) {
                throw e; // Also avoids double wrapping CompletionExceptions below.
            } catch (Throwable t) {
                throw new CompletionException(t);
            }
        });
    }

    /**
     * Delegates the given Callable and Executor to
     * {@link CompletableFuture#completeAsync(Supplier, Executor)} using a new
     * CompletableFuture and handles checked exceptions accordingly to unchecked
     * exceptions.
     *
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param executor the executor to use for asynchronous execution
     * @param          <U> the function's return type
     * @return the new CompletableFuture
     * @see CompletableFuture#completeAsync(Supplier, Executor)
     */
    public static <U> CompletableFuture<U> callAsync(Callable<? extends U> callable, Executor executor) {
        return completeAsync(new CompletableFuture<>(), callable, executor);
    }

    /**
     * Delegates the given Callable and Executor to
     * {@link CompletableFuture#completeAsync(Supplier, Executor)} using the given
     * CompletableFuture and handles checked exceptions accordingly to unchecked
     * exceptions.
     *
     * @param result   the CompletableFuture to be used
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param executor the executor to use for asynchronous execution
     * @param          <T> the function's return type
     * @return the given CompletableFuture
     * @see CompletableFuture#completeAsync(Supplier, Executor)
     */
    public static <T> CompletableFuture<T> completeAsync(CompletableFuture<T> result, Callable<? extends T> callable, Executor executor) {
        return result.completeAsync(callable == null ? null : () -> {
            try {
                return callable.call();
            } catch (Error e) {
                throw e;
            } catch (RuntimeException e) {
                throw e; // Also avoids double wrapping CompletionExceptions below.
            } catch (Throwable t) {
                throw new CompletionException(t);
            }
        }, executor);
    }

    private CompletableFutureUtils() {
        throw new AssertionError("CompletableFutureUtils cannot be instantiated");
    }
}

// Java 8
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;

/**
 * {@link CompletableFuture} utils.
 *
 * @author stonar96
 *
 * @see CompletableFuture
 */
public final class CompletableFutureUtils {
    /**
     * Delegates the given Callable to
     * {@link CompletableFuture#supplyAsync(Supplier)} and handles checked
     * exceptions accordingly to unchecked exceptions.
     *
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param          <U> the function's return type
     * @return the new CompletableFuture
     * @see CompletableFuture#supplyAsync(Supplier)
     */
    public static <U> CompletableFuture<U> callAsync(Callable<? extends U> callable) {
        return CompletableFuture.supplyAsync(callable == null ? null : () -> {
            try {
                return callable.call();
            } catch (Error e) {
                throw e;
            } catch (RuntimeException e) {
                throw e; // Also avoids double wrapping CompletionExceptions below.
            } catch (Throwable t) {
                throw new CompletionException(t);
            }
        });
    }

    /**
     * Delegates the given Callable to
     * {@link CompletableFuture#supplyAsync(Supplier)}, handles checked exceptions
     * accordingly to unchecked exceptions and delegates to and from the given
     * CompletableFuture.
     *
     * @param result   the CompletableFuture to be delegated
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param          <T> the function's return type
     * @return the given delegated CompletableFuture
     * @see CompletableFuture#supplyAsync(Supplier)
     */
    public static <T> CompletableFuture<T> completeAsync(CompletableFuture<T> result, Callable<? extends T> callable) {
        if (result == null) {
            throw new NullPointerException();
        }

        CompletableFuture<T> delegate = callAsync(callable == null ? null : () -> result.isDone() ? null /* (1) */ : callable.call());
        // (1): This is very unlikely because result delegates to delegate (see below).
        // If result is completed, delegate is also completed.
        // Thus supplyAsync will not even call our callable.
        // However, there may be a race condition where they are not already delegated.
        // In this case result will be completed by delegate with null.
        // This does not matter because result is already completed.

        if (delegate == null) {
            return null;
        }

        result.whenComplete((v, t) -> {
            if (t == null) {
                delegate.complete(v);
                return;
            }

            delegate.completeExceptionally(t);
        });
        delegate.whenComplete((v, t) -> {
            if (t == null) {
                result.complete(v);
                return;
            }

            result.completeExceptionally(t);
        });
        return result;
    }

    /**
     * Delegates the given Callable and Executor to
     * {@link CompletableFuture#supplyAsync(Supplier, Executor)} and handles checked
     * exceptions accordingly to unchecked exceptions.
     *
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param executor the executor to use for asynchronous execution
     * @param          <U> the function's return type
     * @return the new CompletableFuture
     * @see CompletableFuture#supplyAsync(Supplier, Executor)
     */
    public static <U> CompletableFuture<U> callAsync(Callable<? extends U> callable, Executor executor) {
        return CompletableFuture.supplyAsync(callable == null ? null : () -> {
            try {
                return callable.call();
            } catch (Error e) {
                throw e;
            } catch (RuntimeException e) {
                throw e; // Also avoids double wrapping CompletionExceptions below.
            } catch (Throwable t) {
                throw new CompletionException(t);
            }
        }, executor);
    }

    /**
     * Delegates the given Callable and Executor to
     * {@link CompletableFuture#supplyAsync(Supplier, Executor)}, handles checked
     * exceptions accordingly to unchecked exceptions and delegates to and from the
     * given CompletableFuture.
     *
     * @param result   the CompletableFuture to be delegated
     * @param callable a function returning the value to be used to complete the
     *                 returned CompletableFuture
     * @param executor the executor to use for asynchronous execution
     * @param          <T> the function's return type
     * @return the given delegated CompletableFuture
     * @see CompletableFuture#supplyAsync(Supplier, Executor)
     */
    public static <T> CompletableFuture<T> completeAsync(CompletableFuture<T> result, Callable<? extends T> callable, Executor executor) {
        if (result == null) {
            throw new NullPointerException();
        }

        CompletableFuture<T> delegate = callAsync(callable == null ? null : () -> result.isDone() ? null /* (1) */ : callable.call(), executor);
        // (1): This is very unlikely because result delegates to delegate (see below).
        // If result is completed, delegate is also completed.
        // Thus supplyAsync will not even call our callable.
        // However, there may be a race condition where they are not already delegated.
        // In this case result will be completed by delegate with null.
        // This does not matter because result is already completed.

        if (delegate == null) {
            return null;
        }

        result.whenComplete((v, t) -> {
            if (t == null) {
                delegate.complete(v);
                return;
            }

            delegate.completeExceptionally(t);
        });
        delegate.whenComplete((v, t) -> {
            if (t == null) {
                result.complete(v);
                return;
            }

            result.completeExceptionally(t);
        });
        return result;
    }

    private CompletableFutureUtils() {
        throw new AssertionError("CompletableFutureUtils cannot be instantiated");
    }
}

As you can see, everything is just delegated as is, except for checked exceptions, which must be handled.

stonar96
  • 1,359
  • 2
  • 11
  • 39
  • 1
    a honest question here: for many years now I've been doing `CompletableFuture.supplyAsync(()->{ try{return myCallable.call();} catch(Exception e){throw new CompletionException(e);} }, myExecutor)` exactly for `myCallable` to go through all the standard mechanisms of `CompletableFuture`. Do you think my approach is missing something? (you can add additional `catch(CompletionException e){throw e;}` there if you expect `myCallable` to ever throw a `CompletionException` to avoid double wrapping) – morgwai Aug 18 '22 at 06:46
  • You are right, I didn't know that `CompletableFuture#supplyAsync` wraps all `Throwable`s into a `CompletionException`. I'm wondering why the thrown `Throwable` isn't just delegated? I think I should update my answer, your code seems fine and more correct than what I do, now that I know that `Throwable`s are wrapped. Your additional code to avoid double wrapping isn't necessary, see https://github.com/openjdk/jdk8u/blob/master/jdk/src/share/classes/java/util/concurrent/CompletableFuture.java#L273. – stonar96 Aug 18 '22 at 18:05
  • 1
    regarding double wrapping: `encodeThrowable(e)` prevents yet another additional wrapping by `CompletableFuture` but if `myCallable` throws a `CompletionException` and my `catch(Exception)` wraps it in second `CompletionException`, then `encodeThrowable(e)` (nor any other code in `CompletableFuture` to my knowledge) will not unwrap it. Therefore I'm pretty sure that if `myCallable` throws a `CompletionException` and there's no additional `catch(CompletionException)` block that propagates it without wrapping, then it will be unnecessarily wrapped by `catch(Exception)` block and passed on... – morgwai Aug 19 '22 at 09:06
  • regarding "why the thrown Throwable isn't just delegated": I haven't read `CompletableFuture` that carefully, but I'm guessing it's for differentiating between exceptions thrown by user provided lambda and internal exceptions of `CompletableFuture` that may happen. This way probably `CompletableFuture` knows that it should pass the exception as an argument to the `BiFunction` provided by user as the argument of `handle` / `whenComplete` and continue the pipeline vs leave the exception uncaught thus aborting the pipeline due to some internal errors. – morgwai Aug 19 '22 at 09:13
  • @morgwai You're right thanks! I've finally edited and corrected my answer now. – stonar96 Nov 14 '22 at 22:46