* ActivityInfo 4.0
* Copyright © 2014-2022 BeDataDriven Groep B.V. - All Rights Reserved
*
* MIT License
*/
package org.activityinfo.promise;
import com.google.common.base.Functions;
import com.google.common.collect.Lists;
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The Promise interface represents a proxy for a value not necessarily known at its creation time.
* It allows you to associate handlers to an asynchronous action's eventual success or failure.
* This let asynchronous methods to return values like synchronous methods: instead of the final value,
* the asynchronous method returns a promise of having a value at some point in the future.
*
* @param <T> the type of the promised value
*/
public final class Promise<T> implements AsyncCallback<T> {
private static final Logger LOGGER = Logger.getLogger(Promise.class.getName());
public enum State {
/**
* The action relating to the promise succeeded
*/
FULFILLED,
/**
* The action relating to the promise failed
*/
REJECTED,
/**
* Hasn't fulfilled or rejected yet
*/
PENDING
}
private boolean fulfilled;
private @Nullable T value;
private @MonotonicNonNull Throwable exception;
private @MonotonicNonNull List<AsyncCallback<? super T>> callbacks = null;
public Promise() {
}
public State getState() {
if(exception != null) {
return State.REJECTED;
} else if(fulfilled) {
return State.FULFILLED;
} else {
return State.PENDING;
}
}
public boolean isSettled() {
return fulfilled || exception != null;
}
public final void resolve(T value) {
if (isSettled()) {
return;
}
this.value = value;
this.fulfilled = true;
publishFulfillment();
}
@SuppressWarnings("nullness:argument") // Complex, if fulfilled = true, then value is @NonNull, but only IF T is also @NonNull
public void then(AsyncCallback<? super T> callback) {
if(exception != null) {
callback.onFailure(exception);
} else if(fulfilled) {
callback.onSuccess(value);
} else {
// Pending...
if (callbacks == null) {
callbacks = Lists.newArrayList();
}
callbacks.add(callback);
}
}
public Promise<T> catch_(Function<Throwable, T> function) {
return this.<T>then(result -> result, function);
}
public <R extends @Nullable Object> Promise<R> join(final Function<? super T, Promise<R>> onFulfilled) {
return join(onFulfilled, null);
}
public <R extends @Nullable Object> Promise<R> join(final Function<@Nullable ? super T, Promise<R>> onFulfilled, final @Nullable Function<Throwable, Promise<R>> onRejected) {
final Promise<R> chained = new Promise<>();
then(new AsyncCallback<T>() {
@Override
public void onFailure(Throwable caught) {
if(onRejected == null) {
chained.onFailure(caught);
} else {
try {
onRejected.apply(caught).then(chained);
} catch (Throwable recaught) {
chained.onFailure(recaught);
}
}
}
@Override
public void onSuccess(T t) {
try {
Promise<R> result = onFulfilled.apply(t);
assert result != null : "function " + onFulfilled + " returned null!!";
result.then(chained);
} catch(Throwable caught) {
chained.onFailure(caught);
}
}
});
return chained;
}
public Promise<Void> thenDiscardResult() {
return then(Functions.constant(null));
}
public <R> Promise<R> join(Supplier<Promise<R>> supplier) {
return join(x -> supplier.get());
}
public Promise<T> thenDo(final Consumer<T> consumer) {
return then(result -> {
consumer.accept(result);
return result;
});
}
public <R> Promise<R> then(final Function<? super T, R> onFulfilled) {
return then(onFulfilled, null);
}
public <R> Promise<R> then(final Function<? super T, R> onFulfilled, final @Nullable Function<Throwable, R> onRejected) {
assert onFulfilled != null : "function is null";
final Promise<R> chained = new Promise<>();
then(new AsyncCallback<T>() {
@Override
public void onFailure(Throwable caught) {
if(onRejected != null) {
try {
chained.resolve(onRejected.apply(caught));
} catch (Throwable recaught) {
chained.reject(recaught);
}
} else {
chained.reject(caught);
}
}
@Override
public void onSuccess(T t) {
try {
chained.resolve(onFulfilled.apply(t));
} catch (Throwable caught) {
chained.reject(caught);
}
}
});
return chained;
}
public <R> Promise<R> then(final Supplier<R> function) {
assert function != null : "function is null";
return then(x -> function.get());
}
@SuppressWarnings("nullness:return")
private T getOrThrowIfNotYetLoaded() {
if(!fulfilled) {
throw new IllegalStateException();
}
return value;
}
public Throwable getException() {
if(exception == null) {
throw new IllegalStateException();
}
return exception;
}
@Override
public void onFailure(Throwable caught) {
reject(caught);
}
@Override
public void onSuccess(T result) {
resolve(result);
}
public final void reject(Throwable caught) {
LOGGER.log(Level.WARNING, "Promise rejected", caught);
if (isSettled()) {
return;
}
this.exception = caught;
publishRejection();
}
@RequiresNonNull("exception")
private void publishRejection() {
if (callbacks != null) {
for (AsyncCallback<? super T> callback : callbacks) {
callback.onFailure(exception);
}
}
}
@SuppressWarnings("nullness:argument")
private void publishFulfillment() {
if (callbacks != null) {
for (AsyncCallback<? super T> callback : callbacks) {
callback.onSuccess(value);
}
}
}
public static <T> Promise<T> resolved(T value) {
Promise<T> promise = new Promise<>();
promise.resolve(value);
return promise;
}
public static Promise<Void> done() {
return Promise.resolved(null);
}
public static <X> Promise<X> rejected(Throwable exception) {
Promise<X> promise = new Promise<>();
promise.reject(exception);
return promise;
}
/**
* Applies an asynchronous function to each of the elements in {@code items},
*/
public static <T> Promise<Void> forEach(Iterable<T> items, final Function<? super T, Promise<Void>> function) {
Promise<Void> promise = Promise.resolved(null);
for(final T item : items) {
promise = promise.join(new Function<Void, Promise<Void>>() {
@Override
public Promise<Void> apply(@Nullable Void input) {
return function.apply(item);
}
});
}
return promise;
}
public static Promise<Void> waitAll(Promise<?>... promises) {
return waitAll(Arrays.asList(promises));
}
public static Promise<Void> waitAll(final List<? extends Promise<?>> promises) {
if(promises.isEmpty()) {
return Promise.done();
}
final Promise<Void> result = new Promise<>();
final int[] remaining = new int[] { promises.size() };
AsyncCallback<Void> callback = new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
result.onFailure(caught);
}
@Override
public void onSuccess(Void o) {
remaining[0]--;
if(remaining[0] == 0) {
result.onSuccess(null);
}
}
};
for(int i=0;i!=promises.size();++i) {
promises.get(i).thenDiscardResult().then(callback);
}
return result;
}
public static <T> Promise<List<T>> flatten(final List<Promise<T>> promises) {
return waitAll(promises).then(new Function<Void, List<T>>() {
@Override
public List<T> apply(@Nullable Void aVoid) {
List<T> items = new ArrayList<>();
for (Promise<T> promise : promises) {
items.add(promise.getOrThrowIfNotYetLoaded());
}
return items;
}
});
}
@Override
public String toString() {
if(fulfilled) {
return "<fulfilled: " + value + ">";
} else if(exception != null) {
return "<rejected: " + exception.getClass().getSimpleName() + ">";
} else {
return "<pending>";
}
}
}
Hi all,
So long long ago, I wrote a Promise class for our GWT project to make easier to write complex async code that ran in the browser but could also be run in Junit tests running in the plain JRE, and sometimes on the server in cases where we could assume the Promise would complete synchronously.
Fast forward 10+ years, and the Promise has long been standardized and baked into browsers themselves, WITH great support in DevTools for following the path of a Promise across resolutions.
I'm refactoring this class to use the browser's native Promise implementation. Normally the way I would do this is:
1. Align the Java implementation class to the Browser's Promise API
2. Add a supersource implementation that uses JSNI to invoke the browser's API when compiling.
BUT - is this still the "best" way to do this in 2025, with @JsTypes?
AND - there is already a Promise class in the elemental library that we use extensively, but it's a "native" class, so we can't use it in the JRE. Can I provide a JRE-safe implementation of elemental2.promise.Promise without monkey-patching elemental2-promise ?
Has anyone else implemented something similar?
Best,
Alex
You received this message because you are subscribed to the Google Groups "GWT Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/google-web-toolkit/710fa0f3-d544-4a59-b065-1a6dfeb25d20n%40googlegroups.com.
No comments:
Post a Comment