arez.browser.extras.ObservablePromise Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of arez-browser-extras Show documentation
Show all versions of arez-browser-extras Show documentation
Arez Browser based Extras and Addons
package arez.browser.extras;
import arez.annotations.Action;
import arez.annotations.ArezComponent;
import arez.annotations.Observable;
import elemental2.promise.IThenable;
import elemental2.promise.Promise;
import java.util.Objects;
import javax.annotation.Nonnull;
import jsinterop.base.Js;
import org.realityforge.anodoc.Unsupported;
import static org.realityforge.braincheck.Guards.*;
/**
* An observable model that wraps a Promise and exposes observable state that track
* the state of the promise. The observable exposes the state of the promise as well
* as the value that it resolves to or the error it was rejected with as observable
* properties.
*
* A very simple example
* {@code
* import com.google.gwt.core.client.EntryPoint;
* import elemental2.dom.DomGlobal;
* import elemental2.dom.Response;
* import elemental2.promise.Promise;
* import arez.Arez;
* import arez.browser.extras.ObservablePromise;
*
* public class Example
* implements EntryPoint
* {
* public void onModuleLoad()
* {
* final Promise promise = DomGlobal.fetch( "https://example.com/" );
* final ObservablePromise observablePromise = ObservablePromise.create( promise );
* Arez.context().autorun( () -> {
* final String message = "Promise Status: " + observablePromise.getState();
* DomGlobal.console.log( message );
* } );
* }
* }
* }
*
* @param the type of the value that the promise will resolve to.
* @param the type of the error if the promise is rejected.
*/
@Unsupported( "This is still considered experimental and will likely evolve over time" )
@ArezComponent
public abstract class ObservablePromise
{
/**
* The state of the promise.
*/
public enum State
{
PENDING, FULFILLED, REJECTED
}
/**
* The underlying promise.
* This is not converted to a local variable to make it easy to debug scenarios from within the
* browsers DevTools.
*/
@SuppressWarnings( "FieldCanBeLocal" )
private final Promise _promise;
/**
* The state of the promise. Starts as {@link State#PENDING} and then transitions to either
* {@link State#FULFILLED} or {@link State#REJECTED}.
*/
@Nonnull
private State _state;
/**
* The value that the promise resolved to. This is not valid unless the state is {@link State#FULFILLED}.
*/
private T _value;
/**
* The error that the promise was rejected with. This is not valid unless the state is {@link State#REJECTED}.
*/
private E _error;
/**
* Create the observable model that wraps specified promise.
*
* @param the type of the value that the promise will resolve to.
* @param the type of the error if the promise is rejected.
* @param promise the promise to wrap.
* @return the ObservablePromise
*/
public static ObservablePromise create( @Nonnull final Promise promise )
{
return new Arez_ObservablePromise<>( promise );
}
ObservablePromise( @Nonnull final Promise promise )
{
_state = State.PENDING;
_promise = Objects.requireNonNull( promise );
_promise.then( this::onFulfilled ).catch_( this::onRejected );
}
/**
* Return the promise state.
*
* @return the promise state.
*/
@Observable
@Nonnull
public State getState()
{
return _state;
}
void setState( @Nonnull final State state )
{
_state = Objects.requireNonNull( state );
}
/**
* Return the value that the promise was resolved to.
* This should NOT be called if the state is not {@link State#FULFILLED} and will result in an invariant
* failure if invariants are enabled.
*
* @return the value that the promise was resolved to.
*/
@Observable
public T getValue()
{
apiInvariant( () -> _state == State.FULFILLED,
() -> "ObservablePromise.getValue() called when the promise is not in fulfilled state. " +
"State: " + _state + ", Promise: " + _promise );
return _value;
}
void setValue( final T value )
{
invariant( () -> _state == State.FULFILLED,
() -> "ObservablePromise.setValue() called when promise is in incorrect state. " +
"State: " + _state + ", Promise: " + _promise );
_value = value;
}
/**
* Return the error that the promise was rejected with.
* This should NOT be called if the state is not {@link State#REJECTED} and will result in an invariant
* failure if invariants are enabled.
*
* @return the error that the promise was rejected with.
*/
@Observable
public E getError()
{
apiInvariant( () -> _state == State.REJECTED,
() -> "ObservablePromise.getError() called when the promise is not in rejected state. " +
"State: " + _state + ", Promise: " + _promise );
return _error;
}
void setError( final E error )
{
invariant( () -> _state == State.REJECTED,
() -> "ObservablePromise.setError() called when promise is in incorrect state. " +
"State: " + _state + ", Promise: " + _promise );
_error = error;
}
@Action
@Nonnull
IThenable onFulfilled( final T value )
{
setState( State.FULFILLED );
setValue( value );
return Promise.resolve( value );
}
@Action
IThenable