All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.promise.Promise Maven / Gradle / Ivy

Go to download

A convenient way to organize asynchronous code. Promises are a faster and more efficient version of JavaScript's Promise and Java's CompletionStage's.

There is a newer version: 6.0-rc2
Show newest version
/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.activej.promise;

import io.activej.async.callback.AsyncComputation;
import io.activej.async.callback.Callback;
import io.activej.async.function.*;
import io.activej.common.collection.Try;
import io.activej.common.function.*;
import io.activej.reactor.Reactor;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static io.activej.common.Checks.checkArgument;
import static io.activej.common.exception.FatalErrorHandler.getExceptionOrThrowError;
import static io.activej.common.exception.FatalErrorHandler.handleError;
import static io.activej.reactor.Reactor.getCurrentReactor;

/**
 * Replacement of default Java {@link CompletionStage} interface with
 * optimized design, which allows handling different scenarios more
 * efficiently.
 * 

* Each promise represents some sort of operations executed * after the previous {@code Promise} completes. *

* {@code Promise} can complete either successfully with a result * which will be wrapped inside the {@code Promise} or exceptionally, * returning a new {@link CompletePromise} or {@link CompleteExceptionallyPromise} * respectively. *

* {@link SettablePromise} allows to create a root for chain of {@code Promise}s. *

* * @see CompletionStage */ public interface Promise extends AsyncComputation { /** * Creates successfully completed {@code Promise} */ @SuppressWarnings("unchecked") static Promise complete() { return (Promise) CompleteNullPromise.INSTANCE; } /** * Creates successfully completed {@code Promise}. * * @param value result of Promise. If value is {@code null}, * returns {@link CompleteNullPromise}, otherwise * {@link CompleteResultPromise} */ static Promise of(@Nullable T value) { return value != null ? new CompleteResultPromise<>(value) : CompleteNullPromise.instance(); } /** * Creates an exceptionally completed {@code Promise}. * * @param e Exception */ static Promise ofException(Exception e) { return new CompleteExceptionallyPromise<>(e); } /** * Creates and returns a new {@link SettablePromise} * that is accepted by the provided {@link ConsumerEx} of * {@link SettablePromise} */ static Promise ofCallback(CallbackSupplierEx fn) { SettablePromise cb = new SettablePromise<>(); try { fn.get(cb); } catch (Exception ex) { handleError(ex, fn); return Promise.ofException(ex); } return cb; } static Promise ofCallback(T value, CallbackFunctionEx fn) { SettablePromise cb = new SettablePromise<>(); try { fn.apply(value, cb); } catch (Exception ex) { handleError(ex, fn); return Promise.ofException(ex); } return cb; } static Promise ofCallback(T value, @Nullable Exception exception, CallbackBiFunctionEx fn) { SettablePromise cb = new SettablePromise<>(); try { fn.apply(value, exception, cb); } catch (Exception ex) { handleError(ex, fn); return Promise.ofException(ex); } return cb; } static Promise ofCallback( T value, @Nullable Exception exception, CallbackFunctionEx fn, CallbackFunctionEx fnException ) { SettablePromise cb = new SettablePromise<>(); try { if (exception == null) fn.apply(value, cb); else fnException.apply(exception, cb); } catch (Exception ex) { handleError(ex, fn); return Promise.ofException(ex); } return cb; } /** * Creates a new {@code Promise} of the given value * * @see #ofOptional(Optional, Supplier) */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") static Promise ofOptional(Optional optional) { return ofOptional(optional, NoSuchElementException::new); } /** * Creates a new {@code Promise} of the given value. * If {@code Optional} doesn't equal {@code null}, a * {@code Promise} of {@code optional} contained value * will be created. Otherwise, a {@code Promise} with * {@code errorSupplier} exception will be created. * * @return {@link CompletePromise} if the optional value * doesn't equal {@code null}, otherwise * {@link CompleteExceptionallyPromise} with * {@code errorSupplier} exception. */ @SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "OptionalIsPresent"}) static Promise ofOptional(Optional optional, Supplier errorSupplier) { if (optional.isPresent()) return Promise.of(optional.get()); return Promise.ofException(errorSupplier.get()); } /** * Creates a completed {@code Promise} from {@code T value} and * {@code Exception e} parameters, any of them can be {@code null}. * Useful for {@link #then} passthroughs * (for example, when mapping specific exceptions). * * @param value value to wrap when exception is null * @param e possibly-null exception, determines type of promise completion */ static Promise of(@Nullable T value, @Nullable Exception e) { checkArgument(!(value != null && e != null), "Either value or exception should be 'null'"); return e == null ? of(value) : ofException(e); } /** * Returns a new {@link CompletePromise} or {@link CompleteExceptionallyPromise} * based on the provided {@link Try}. */ static Promise ofTry(Try t) { return t.reduce(Promise::of, Promise::ofException); } /** * Creates a {@code Promise} wrapper around default * Java {@code CompletableFuture} and runs it immediately. * * @return a new {@code Promise} with a result of the given future */ static Promise ofFuture(CompletableFuture future) { return ofCompletionStage(future); } /** * Wraps Java {@link CompletionStage} in a {@code Promise}, running it in current reactor. * * @param completionStage completion stage itself * @return result of the given completionStage wrapped in a {@code Promise} */ static Promise ofCompletionStage(CompletionStage completionStage) { return ofCallback(cb -> { Reactor reactor = getCurrentReactor(); reactor.startExternalTask(); completionStage.whenCompleteAsync((result, throwable) -> { reactor.execute(() -> { if (throwable == null) { cb.set(result, null); } else { Exception e = getExceptionOrThrowError(throwable instanceof CompletionException ? throwable.getCause() : throwable); handleError(e, cb); cb.set(null, e); } }); reactor.completeExternalTask(); }); }); } /** * Wraps Java {@code Future} in a {@code Promise} running it with given {@link Executor}. * * @param executor executor to execute the future concurrently * @param future the future itself * @return a new {@code Promise} of the future result */ static Promise ofFuture(Executor executor, Future future) { return ofCallback(cb -> { Reactor reactor = Reactor.getCurrentReactor(); reactor.startExternalTask(); try { executor.execute(() -> { try { T value = future.get(); reactor.execute(() -> cb.set(value)); } catch (ExecutionException ex) { reactor.execute(() -> { Exception e = getExceptionOrThrowError(ex.getCause()); handleError(e, cb); cb.setException(e); }); } catch (InterruptedException e) { Thread.currentThread().interrupt(); reactor.execute(() -> cb.setException(e)); } catch (CancellationException e) { reactor.execute(() -> cb.setException(e)); } catch (Throwable throwable) { reactor.execute(() -> { Exception e = getExceptionOrThrowError(throwable); handleError(e, cb); cb.setException(e); }); } finally { reactor.completeExternalTask(); } }); } catch (RejectedExecutionException e) { reactor.completeExternalTask(); cb.setException(e); } }); } /** * Runs some task in another thread (executed by a given {@code Executor}) * and returns a {@code Promise} for it. Also manages external task count * for current reactor, so it won't shut down until the task is complete. * * @param executor executor to execute the task concurrently * @param supplier the task itself * @return {@code Promise} for the given task */ static Promise ofBlocking(Executor executor, SupplierEx supplier) { return ofCallback(cb -> { Reactor reactor = Reactor.getCurrentReactor(); reactor.startExternalTask(); try { executor.execute(() -> { try { T result = supplier.get(); reactor.execute(() -> cb.set(result)); } catch (Throwable throwable) { reactor.execute(() -> { Exception e = getExceptionOrThrowError(throwable); handleError(e, cb); cb.setException(e); }); } finally { reactor.completeExternalTask(); } }); } catch (RejectedExecutionException e) { reactor.completeExternalTask(); cb.setException(e); } }); } /** * Same as {@link #ofBlocking(Executor, SupplierEx)}, but without a result * (returned {@code Promise} is only a marker of completion). */ static Promise ofBlocking(Executor executor, RunnableEx runnable) { return Promise.ofBlocking(executor, () -> { runnable.run(); return null; }); } @Contract(pure = true) default boolean isComplete() { return isResult() || isException(); } @Contract(pure = true) boolean isResult(); @Contract(pure = true) boolean isException(); @Contract(pure = true) T getResult(); @Contract(pure = true) Exception getException(); @Contract(pure = true) Try getTry(); /** * Ensures that {@code Promise} completes asynchronously: * if this {@code Promise} is already completed, its * completion will be posted to the next reactor tick. * Otherwise, does nothing. */ @Contract(pure = true) Promise async(); Promise subscribe(Callback cb); /** * Returns a new {@code Promise} which is obtained by mapping * a result of {@code this} promise to some other value. * If {@code this} promise is completed exceptionally, a mapping * function will not be applied. * *

* A mapping function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a function to map the result of this {@code Promise} * to a new value * @return new {@code Promise} whose result is the result of function * applied to the result of {@code this} promise * @see CompletionStage#thenApply(Function) */ default Promise map(FunctionEx fn) { return thenCallback((t, cb) -> cb.set(fn.apply(t))); } /** * Returns a new {@code Promise} which is obtained by mapping * a result and an exception of {@code this} promise to some other value. * If {@code this} promise is completed exceptionally, an exception * passed to a mapping function is guaranteed to be not null. * *

* A {bi function} may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a function to map a result and * an exception of {@code this} promise to some other value * @return new {@code Promise} whose result is the result of function * applied to the result and exception of {@code this} promise */ default Promise map(BiFunctionEx fn) { return thenCallback((t, e, cb) -> cb.set(fn.apply(t, e))); } /** * Returns a new {@code Promise} which is obtained by mapping * either a result or an exception of {@code this} promise to some other values. * If {@code this} promise is completed successfully, the first function will be called mapping the * result to some other value. Otherwise, second function will be called mapping the exception * to some other value. * *

* Each function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a function to map a result of {@code this} promise to some other value * @param exceptionFn a function to map an exception of {@code this} promise to some other value * @return new {@code Promise} whose result is the result of either first or second * function applied either to a result or an exception of {@code this} promise. */ default Promise map(FunctionEx fn, FunctionEx exceptionFn) { return thenCallback((t, e, cb) -> cb.set(e == null ? fn.apply(t) : exceptionFn.apply(e))); } /** * Returns a new {@code Promise} which is obtained by mapping * an exception of {@code this} promise to some other exception. * The mapping function will be called only if {@code this} promise * completes exceptionally * *

* A mapping function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param exceptionFn a function to map an exception of {@code this} promise to some other value * @return new {@code Promise} whose result is the result of a mapping * function applied either to an exception of {@code this} promise. */ default Promise mapException(FunctionEx exceptionFn) { return thenCallback((t, e, cb) -> { if (e == null) cb.set(t); else cb.setException(exceptionFn.apply(e)); }); } /** * Returns a new {@code Promise} which is obtained by mapping * an exception of {@code this} promise to some other exception. * The mapping function will be called only if {@code this} promise * completes with an exception that is an instance of a given {@link Class} * *

* A mapping function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param clazz an exception class that exception is tested against. A mapping function is applied * only if an exception of {@code this} promise is an instance of the specified class * @param exceptionFn a function to map an exception of {@code this} promise to some other value * @return new {@code Promise} whose result is the result of a mapping * function applied either to an exception of {@code this} promise. */ @SuppressWarnings("unchecked") default Promise mapException( Class clazz, FunctionEx exceptionFn ) { return thenCallback((t, e, cb) -> { if (e == null) cb.set(t); else cb.setException(clazz.isAssignableFrom(e.getClass()) ? exceptionFn.apply((E) e) : e); }); } /** * Returns a new {@code Promise} which is obtained by calling * a provided supplier of a new promise. If {@code this} promise * is completed exceptionally, a supplier will not be called. * *

* A supplier may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a supplier of a new promise which will be called * if {@code this} promise completes successfully */ default Promise then(AsyncSupplierEx fn) { return thenCallback(cb -> fn.get().subscribe(cb)); } default Promise thenCallback(CallbackSupplierEx fn) { return thenCallback((t, e, cb) -> { if (e == null) fn.get(cb); else cb.setException(e); }); } /** * Returns a new {@code Promise} which is obtained by mapping * a result of {@code this} promise to some other promise. * If {@code this} promise is completed exceptionally, a mapping * function will not be applied. * *

* A mapping function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a function to map the result of this {@code Promise} * to a new promise * @return new {@code Promise} which is the result of function * applied to the result of {@code this} promise * @see CompletionStage#thenCompose(Function) */ default Promise then(AsyncFunctionEx fn) { return thenCallback((t, cb) -> fn.apply(t).subscribe(cb)); } default Promise thenCallback(CallbackFunctionEx fn) { return thenCallback((t, e, cb) -> { if (e == null) fn.apply(t, cb); else cb.setException(e); }); } /** * Returns a new {@code Promise} which is obtained by mapping * a result and an exception of {@code this} promise to some other promise. * If {@code this} promise is completed exceptionally, an exception * passed to a mapping function is guaranteed to be not null. * *

* A function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a function to map a result and * an exception of {@code this} promise to some other promise * @return new {@code Promise} which is the result of function * applied to the result and exception of {@code this} promise */ default Promise then(AsyncBiFunctionEx fn) { return thenCallback((t, e, cb) -> fn.apply(t, e).subscribe(cb)); } Promise thenCallback(CallbackBiFunctionEx fn); /** * Returns a new {@code Promise} which is obtained by mapping * either a result or an exception of {@code this} promise to some other promises. * If {@code this} promise is completed successfully, the first function will be called mapping the * result to a promise of some other value. Otherwise, second function will be called mapping the exception * to a promise of some other value. * *

* Each function may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a function to map a result of {@code this} promise to a different promise * @param exceptionFn a function to map an exception of {@code this} promise a different promise * @return new {@code Promise} which is a result of either first or second * function applied either to a result or an exception of {@code this} promise. */ default Promise then(AsyncFunctionEx fn, AsyncFunctionEx exceptionFn) { return thenCallback((t, e, cb) -> (e == null ? fn.apply(t) : exceptionFn.apply(e)).subscribe(cb)); } default Promise thenCallback( CallbackFunctionEx fn, CallbackFunctionEx exceptionFn ) { return thenCallback((t, e, cb) -> { if (e == null) fn.apply(t, cb); else exceptionFn.apply(e, cb); }); } /** * Subscribes given consumer to be executed * after this {@code Promise} completes (either successfully or exceptionally). * Returns a new {@code Promise}. * *

* A consumer may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn a consumer that consumes a result * and an exception of {@code this} promise */ default Promise whenComplete(BiConsumerEx fn) { return thenCallback((t, e, cb) -> { fn.accept(t, e); cb.set(t, e); }); } /** * Subscribes given consumers to be executed * after {@code this} Promise completes (either successfully or exceptionally). * The first consumer will be executed if {@code this} Promise completes * successfully, otherwise the second promise will be executed. * Returns a new {@code Promise}. * *

* Each consumer may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn consumer that consumes a result of {@code this} promise * @param exceptionFn consumer that consumes an exception of {@code this} promise */ default Promise whenComplete(ConsumerEx fn, ConsumerEx exceptionFn) { return thenCallback((t, e, cb) -> { fn.accept(t); cb.set(t, e); }); } /** * Subscribes given runnable to be executed * after this {@code Promise} completes (either successfully or exceptionally). * Returns a new {@code Promise}. * *

* A runnable may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param action runnable to be executed after {@code this} promise completes */ default Promise whenComplete(RunnableEx action) { return thenCallback((t, e, cb) -> { action.run(); cb.set(t, e); }); } /** * Subscribes given consumer to be executed * after this {@code Promise} completes successfully. * Returns a new {@code Promise}. * *

* A consumer may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn consumer that consumes a result of {@code this} promise */ default Promise whenResult(ConsumerEx fn) { return thenCallback((t, cb) -> { fn.accept(t); cb.set(t); }); } /** * Subscribes given runnable to be executed * after this {@code Promise} completes successfully * and a result of this {@code Promise} satisfy a given {@link Predicate}. * Returns a new {@code Promise}. * *

* A runnable may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param action runnable to be executed after {@code this} promise * completes successfully */ default Promise whenResult(RunnableEx action) { return thenCallback((t, cb) -> { action.run(); cb.set(t); }); } /** * Subscribes given consumer to be executed * after this {@code Promise} completes exceptionally. * Returns a new {@code Promise}. * *

* A consumer may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param fn consumer that consumes an exception of {@code this} promise */ default Promise whenException(ConsumerEx fn) { return thenCallback((t, e, cb) -> { if (e != null) fn.accept(e); cb.set(t, e); }); } /** * Subscribes given consumer to be executed * after this {@code Promise} completes exceptionally * and an exception of this {@code Promise} is an instance of a given exception {@link Class}. * Returns a new {@code Promise}. * *

* A consumer may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param clazz an exception class that exception is tested against. A consumer is called * only if an exception of {@code this} promise is an instance of the specified class * @param fn consumer that consumes an exception of {@code this} promise */ @SuppressWarnings("unchecked") default Promise whenException(Class clazz, ConsumerEx fn) { return thenCallback((t, e, cb) -> { if (e != null && clazz.isAssignableFrom(e.getClass())) fn.accept((E) e); cb.set(t, e); }); } /** * Subscribes given runnable to be executed * after this {@code Promise} completes exceptionally. * Returns a new {@code Promise}. * *

* A runnable may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param action runnable to be executed after {@code this} promise * completes exceptionally */ default Promise whenException(RunnableEx action) { return thenCallback((t, e, cb) -> { if (e != null) action.run(); cb.set(t, e); }); } /** * Subscribes given runnable to be executed * after this {@code Promise} completes exceptionally * and an exception of this {@code Promise} is an instance of a given exception {@link Class}. * Returns a new {@code Promise}. * *

* A runnable may throw a checked exception. In this case * the resulting promise is completed exceptionally with a * thrown exception. * * @param clazz an exception class that exception is tested against. A consumer is called * only if an exception of {@code this} promise is an instance of the specified class * @param action runnable to be executed after {@code this} promise * completes exceptionally and an exception of this {@code Promise} * is an instance of a given exception {@link Class}. */ default Promise whenException(Class clazz, RunnableEx action) { return thenCallback((t, e, cb) -> { if (e != null && clazz.isAssignableFrom(e.getClass())) action.run(); cb.set(t, e); }); } /** * Casts {@code this} promise to a promise of some other type {@link U} *

* Usage example: *

	 * Promise<Object> promise = Promise.of(255);
	 * promise.<Integer>cast()
	 *      .map(Integer::toHexString)
	 *      .whenResult(hex -> System.out.println(hex));
	 * 
* * @param a type to which a promise result will be cast * @return a promise of type {@link U} * @throws ClassCastException if a promise result is not {@code null} * and a cast could not be made */ @SuppressWarnings("unchecked") default Promise cast() { return (Promise) this; } /** * Casts {@code this} promise to a promise of some other type {@link U} *

* Usage example: *

	 * Promise<Object> promise = Promise.of(255);
	 * promise.cast(Integer.class)
	 *      .map(Integer::toHexString)
	 *      .whenResult(hex -> System.out.println(hex));
	 * 
* * @param cls a class to which a result of {@code this} promise will be cast * @param a type to which a promise result will be cast * @return a promise of type {@link U} * @throws ClassCastException if a promise result is not {@code null} * and a cast could not be made */ @SuppressWarnings("unchecked") default Promise cast(Class cls) { return (Promise) this; } /** * Returns a new {@code Promise} that, when this and the other * given {@code Promise} both complete, is executed with the two * results as arguments to the supplied function. * * @param other the other {@code Promise} * @param fn the function to use to compute the value of * the returned {@code Promise} * @return new {@code Promise} */ @Contract(pure = true) Promise combine(Promise other, BiFunctionEx fn); /** * Returns a new {@code Promise} when both * this and provided {@code other} * {@code Promises} complete. * * @param other the other {@code Promise} * @return {@code Promise} of {@code null} * when both this and other * {@code Promise} complete */ @Contract(pure = true) Promise both(Promise other); /** * Returns the {@code Promise} which was completed first. * * @param other the other {@code Promise} * @return the first completed {@code Promise} */ @Contract(pure = true) Promise either(Promise other); /** * Returns {@code Promise} that always completes successfully * with result or exception wrapped in {@link Try}. */ @Contract(pure = true) Promise> toTry(); /** * Waits for result and discards it. */ @Contract(pure = true) Promise toVoid(); /** * Calls a {@code callback} after {@code this} promise is completed, passing * both a result and an exception of a promise to the callback. This method is * similar to {@link #whenComplete(BiConsumerEx)} but does not create or return * a new promise. A {@link Callback} interface also prohibits throwing checked exceptions. *

* In most cases {@link #whenComplete(BiConsumerEx)} is preferred. Fall back to using this method * only if you expect a performance hit from constantly calling {@link #whenComplete(BiConsumerEx)} * and creating new promises as a result. * * @param cb a callback to be called once a promise completes */ @ApiStatus.Internal void next(NextPromise cb); @ApiStatus.Internal @Override default void call(Callback cb) { subscribe(cb); } /** * Wraps {@code Promise} into {@link CompletableFuture}. */ @Contract(pure = true) CompletableFuture toCompletableFuture(); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy