In my GWT Application I'm often refering several times to the same server results. I also don't know which code is executed first. I therefore want to use caching of my asynchronous (client-side) results.
I want to use an existing caching library; I'm considering guava-gwt.
I found this example of a Guava synchronous cache (in guava's documentation):
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
This is how I'm trying to use a Guava cache asynchronously (I have no clue about how to make this work):
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
// I want to do something asynchronous here, I cannot use Thread.sleep in the browser/JavaScript environment.
service.createExpensiveGraph(key, new AsyncCallback<Graph>() {
public void onFailure(Throwable caught) {
// how to tell the cache about the failure???
}
public void onSuccess(Graph result) {
// how to fill the cache with that result???
}
});
return // I cannot provide any result yet. What can I return???
}
});
GWT is missing many classes from the default JRE (especially concerning threads and concurrancy).
How can I use guava-gwt to cache asynchronous results?
(Guava caches are local to a single run of your application. They do not store data in files, or on outside servers. If this does not fit your needs, consider a tool like Memcached.)
Guava Caches store values in RAM.
A semi-persistent mapping from keys to values. Values are automatically loaded by the cache, and are stored in the cache until either evicted or manually invalidated. Implementations of this interface are expected to be thread-safe, and can be safely accessed by multiple concurrent threads.
As I understood what you want to achieve is not just a asynchronous cache but also a lazy cache and to create one the GWT is not a best place as there is a big problem when implementing a GWT app with client side Asynchronous executions, as GWT lacks the client side implementations of Future
s and/or Rx components (still there are some implementations of RxJava for GWT). So in usual java what you want to create can be achieved by :
LoadingCache<String, Future<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, Future<String>>() {
public Future<String> load(String key) {
ExecutorService service = Executors.newSingleThreadExecutor();
return service.submit(()->service.createExpensiveGraph(key));
}
});
Future<String> value = graphs.get("Some Key");
if(value.isDone()){
// This will block the execution until data is loaded
String success = value.get();
}
But as GWT has no implementations for Future
s you need to create one just like
public class FutureResult<T> implements AsyncCallback<T> {
private enum State {
SUCCEEDED, FAILED, INCOMPLETE;
}
private State state = State.INCOMPLETE;
private LinkedHashSet<AsyncCallback<T>> listeners = new LinkedHashSet<AsyncCallback<T>>();
private T value;
private Throwable error;
public T get() {
switch (state) {
case INCOMPLETE:
// Do not block browser so just throw ex
throw new IllegalStateException("The server response did not yet recieved.");
case FAILED: {
throw new IllegalStateException(error);
}
case SUCCEEDED:
return value;
}
throw new IllegalStateException("Something very unclear");
}
public void addCallback(AsyncCallback<T> callback) {
if (callback == null) return;
listeners.add(callback);
}
public boolean isDone() {
return state == State.SUCCEEDED;
}
public void onFailure(Throwable caught) {
state = State.FAILED;
error = caught;
for (AsyncCallback<T> callback : listeners) {
callback.onFailure(caught);
}
}
public void onSuccess(T result) {
this.value = result;
state = State.SUCCEEDED;
for (AsyncCallback<T> callback : listeners) {
callback.onSuccess(value);
}
}
}
And your implementation will become :
LoadingCache<String, FutureResult<String>> graphs = CacheBuilder.newBuilder().build(new CacheLoader<String, FutureResult<String>>() {
public FutureResult<String> load(String key) {
FutureResult<String> result = new FutureResult<String>();
return service.createExpensiveGraph(key, result);
}
});
FutureResult<String> value = graphs.get("Some Key");
// add a custom handler
value.addCallback(new AsyncCallback<String>() {
public void onSuccess(String result) {
// do something
}
public void onFailure(Throwable caught) {
// do something
}
});
// or see if it is already loaded / do not wait
if (value.isDone()) {
String success = value.get();
}
When using the FutureResult
you will not just cache the execution but also get some kind of laziness so you can show some loading screen
while the data is loaded into cache.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With