What would be the correct way of calling JavaScript synchronously?
I have a js.file with a method wrapping a $jQuery.ajax(…).done(…).fail(…); and I call it with something like
PendingJavaScriptResult scriptResult = UI.getCurrent().getPage().executeJs("return sign($0)", new String(data));
scriptResult.then(onSuccess, onFail);
But even if I put this code in a Runnable and give to an executor (together with a CountdownLatch which is ticked down in the Runnables response handling methods), waiting on the latch after submitting locks up the whole thread and the Runnable doesn’t do anything. Should the asynch → synch stuff be done in the JavaScript or is there some standard workaround for this on the Java side?
Edit: even if I put async:false in the jQuery call, executeJS is apparently asynchronous and uses the callbacks in the same manner(?)
Ngh. It looks like the JS is only sent to to browser after the lifecycle completes? Is there any way this can be forced? I’m extending the Java Digital Signature API for an XML document and somewhere in the middle where the signature is generated, I have to use JavaScript to post the sign request to an endpoint running on localhost in order to get the signature and move on. It’s unlikely that I’ll find a way to stay in the API chain if I’ll have to jump out and use asynchronous callbacks
executeJs and all its derivatives are always asynchronous. What you can maybe do in this kind of situation is to run the whole procedure in a dedicated worker thread instead of running it from the “main” thread that handles updates to the Vaadin UI. You would then use ui.access to submit the executeJs job to a Vaadin thread and have the worker thread block on e.g. a CompletableFuture that will be completed from a Vaadin thread that eventually gets the result back from the user’s browser.
Leif Åstrand: executeJs and all its derivatives are always asynchronous. What you can maybe do in this kind of situation is to run the whole procedure in a dedicated worker thread instead of running it from the “main” thread that handles updates to the Vaadin UI. You would then use ui.access to submit the executeJs job to a Vaadin thread and have the worker thread block on e.g. a CompletableFuture that will be completed from a Vaadin thread that eventually gets the result back from the user’s browser.
Tried spawning a separate thread given an UI instance. If I do UI.push() explicitly I see the script being executed but the CompletableFuture get() apparently halts the script execution also because it gets stuck at that point.
Is there any other way of executing javascript from Vaadin than executeJS? I’ve been trying literally everthing for a week now (threads, CDI observers etc).
UI.push() is not enough. You must allow the session lock to be released so that the request that asynchronously brings the return value back to the server can be processed.
In practice, this means that the worker thread does something along these lines
CompletableFuture<Integer> future = new CompletableFuture<>();
ui.access(() -> {
ui.getPage().executeJs("return 1+1").then(Integer.class, value -> future.complete(value));
});
Integer result = future.get();
Alternatively, you can use toCompletableFuture() on the pending JS result. In that case, you need to pass it through an instance field or an explicit handover structure since you cannot modify local variables from inside the thread. If you pass through a field, then need to use accessSynchronously instead of access so that the field is actually written before you block on its value.
Leif Åstrand: UI.push() is not enough. You must allow the session lock to be released so that the request that asynchronously brings the return value back to the server can be processed.
In practice, this means that the worker thread does something along these lines
CompletableFuture<Integer> future = new CompletableFuture<>();
ui.access(() -> {
ui.getPage().executeJs("return 1+1").then(Integer.class, value -> future.complete(value));
});
Integer result = future.get();
Alternatively, you can use toCompletableFuture() on the pending JS result. In that case, you need to pass it through an instance field or an explicit handover structure since you cannot modify local variables from inside the thread. If you pass through a field, then need to use accessSynchronously instead of access so that the field is actually written before you block on its value.
Ah, thanks for the pointer. It looks like I have been using the .get() too soon, thinking the stuff already executes in the browser but I’ve actually deadlocked the calling thread. I think it should be doable now.
it is not practical to call Synchronously because you don’t know how long you need to wait for response.
you can refer to code below for async call. however still problem because you still don’t know when you will get return and this timing is out of your control:
package com.lecompany.iad.presenter;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.page.Page;
import com.vaadin.flow.component.page.PendingJavaScriptResult;
/**
* This must NOT be a singleton
*
* @author Thang Le - www.lecompany.co
* @version 04/2020
*
*/
public class ClientJSCallPresenter {
/** logger. */
private static final Logger LOG = LogManager.getLogger(ClientJSCallPresenter.class);
private Timer locationTimer = null;
private String currentGeoLocation = "0,0";
private UI ui;
public ClientJSCallPresenter(UI ui) {
this.ui = ui;
}
public String getGeoLocation() {
return currentGeoLocation;
}
/**
* get Geolocation by asynchronize calling JS in browser.
*
* This function should not be called inside an UI event handler because JS will
* only be executed after JS of this UI component completed on client side. So
* we cannot get return value inside an UI event handler immediately.
*
*/
public void getGeoLocationJS() {
try {
// UI currentUI = UI.getCurrent();
Page page = this.ui.getPage();
if (page != null && ui != null && ui.isClosing() == false) {
ui.access(() -> {
// get updated location
PendingJavaScriptResult pendingJavaScriptResult = page.executeJs("return returnGetLocation()");
pendingJavaScriptResult.then(String.class, response -> {
currentGeoLocation = (String) response;
LOG.debug("ClientJSCallPresenter getGeoLocationJS Client Res: " + currentGeoLocation);
});
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* this timer to repeatedly update location by call JS from client
*/
public void startGetLocationTimer() {
if (locationTimer == null) {
locationTimer = new Timer("GetLocationTimer");
TimerTask taskGetLocation = new TimerTask() {
public void run() {
getGeoLocationJS();
}
};
locationTimer.schedule(taskGetLocation, 5000, 30000);
}
}
/**
* stop timer to free up resource
*/
public void stopGetLocationTimer() {
// stop timer
if (locationTimer != null) {
locationTimer.cancel();
locationTimer = null;
}
LOG.debug("stopGetLocationTimer");
}
/**
* check whether timer already started or not
*
* @return true: Timer started. false: timer stopped
*/
public boolean isTimerStarted() {
boolean ret = false;
if (locationTimer != null) {
ret = true;
}
return ret;
}
}
Special circumstances. I’m overriding a method in the Java XML digital signature API where I have to get the signature (which is done by calling a script that does a ajax post to a server on the localhost, integrating with a SmartCard, prompting the user for a PIN etc). So a) there is human interaction involved and b) I have no convinient way of “jumping out and continuing asynchronously”
Hmm. Now I get the javascript to run in a separate thread with a callback like
@RouteScoped
@Route("test2")
@NoLoginRequired
@Push
public class TestView2 extends Div implements BeforeEnterObserver {
private static final long serialVersionUID = 1L;
private ExecutorService executor = Executors.newCachedThreadPool();
@PostConstruct
public void init() {
add(getComponent());
}
private class Worker implements Runnable {
private String data;
private UI ui;
private CompletableFuture<Result<SignResponse>> future = new CompletableFuture<>();
private Consumer<Result<SignResponse>> onCompletion;
public Worker(String data, UI ui, Consumer<Result<SignResponse>> onCompletion) {
this.data = data;
this.ui = ui;
this.onCompletion = onCompletion;
}
@Override
public void run() {
Result<SignResponse> result;
ui.access(() -> ui.getPage().executeJs("return sign($0)", data).then(this::handleSucess, this::handleFailure));
try {
result = future.get();
onCompletion.accept(result);
} catch (InterruptedException | ExecutionException e) {
onCompletion.accept(Result.exception(e));
}
}
private void handleSucess(JsonValue value) {
Gson gson = new Gson();
SignResponse signResponse = gson.fromJson(value.asString(), SignResponse.class);
future.complete(Result.ok(signResponse));
}
private void handleFailure(String value) {
future.complete(Result.error(value));
}
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
UI.getCurrent().getPage().addJavaScript("js/script.js");
}
private void onCompletion(Result<SignResponse> response) {
System.out.println("Got " + response);
}
private Component getComponent() {
return new Button(" Test ", e -> executor.submit(new Worker("foo", UI.getCurrent(), this::onCompletion)));
}
}
But if I actually try to block on the future like
@RouteScoped
@Route("test3")
@NoLoginRequired
@Push
public class TestView3 extends Div implements BeforeEnterObserver {
private static final long serialVersionUID = 1L;
private ExecutorService executor = Executors.newCachedThreadPool();
@PostConstruct
public void init() {
add(getComponent());
}
private class Worker implements Runnable {
private String data;
private UI ui;
private CompletableFuture<Result<SignResponse>> future;
public Worker(String data, UI ui, CompletableFuture<Result<SignResponse>> future) {
this.data = data;
this.ui = ui;
this.future = future;
}
@Override
public void run() {
ui.access(() -> ui.getPage().executeJs("return sign($0)", data).then(this::handleSucess,
this::handleFailure));
}
private void handleSucess(JsonValue value) {
Gson gson = new Gson();
SignResponse signResponse = gson.fromJson(value.asString(), SignResponse.class);
future.complete(Result.ok(signResponse));
}
private void handleFailure(String value) {
future.complete(Result.error(value));
}
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
UI.getCurrent().getPage().addJavaScript("js/script.js");
}
private Component getComponent() {
return new Button(" Test ", e -> {
CompletableFuture<Result<SignResponse>> future = new CompletableFuture<>();
executor.submit(new Worker("foo", UI.getCurrent(), future));
try {
Notification.show(future.get().toString());
} catch (InterruptedException | ExecutionException e1) {
e1.printStackTrace();
}
});
}
}
I’m again stuck without script execution. I thought the worker would happily do the work and then the future.complete would release the future.get in the main thread
You still have the same problem in your second example. Calling future.get() in the click listener means that control will not return back to the framework which means that the lock that protects the UI state from concurrent updates from multiple requests or worker threads won’t be released. This in turn means that the request that posts the JS return value back to the server cannot be processed since doing that requires first acquiring the UI state lock.
If you want to show the notification, then you need to do it in the handleSucess method since it will be called on the thread that handles the request that brings the return value back.
Leif Åstrand:
You still have the same problem in your second example. Calling future.get() in the click listener means that control will not return back to the framework which means that the lock that protects the UI state from concurrent updates from multiple requests or worker threads won’t be released. This in turn means that the request that posts the JS return value back to the server cannot be processed since doing that requires first acquiring the UI state lock.
If you want to show the notification, then you need to do it in the handleSucess method since it will be called on the thread that handles the request that brings the return value back.
Indeed. So am I back at the original problem that if I have something like
there is no way to do this is one method because every attempt at waiting for a result results in the entire thread blocking. There is no way of running a separate UI.access that would just do its stuff separate from the main UI thread.
So the only alternative would be to throw the request at a static web page or servlet outside of the Vaadin lifecycle, but that is probably not doable within a single method either?
One really ugly method that comes to mind is trying to modify the whole chain so that everything is done twice, if the CompletableFuture handle is null, spawn a thread and keep a reference to the CF. On the second run, block on the CF.get. But can even that “double call” be done from a single user action in Vaadin?
You can do as in my original suggestion with a worker thread as long as you adhere to a single principle: only block on JS return values on the worker thread (outside ui.access). You’re free to block on database queries or such on the UI thread (i.e. in event handlers or inside ui.access), but blocking on JS return values is not possible.
As an example, I implemented this simple number guessing game with a worker thread that transparently blocks on JS return values. One thing to notice is that I use ui.access to submit the final outcome back to the UI in the form of a notification.
@Route("")
@Push
public class Test extends Div {
public Test() {
add(new Button("Play a game",
event -> startGameThread(UI.getCurrent())));
}
private static void startGameThread(UI ui) {
new Thread(() -> {
int number = ThreadLocalRandom.current().nextInt(10) + 1;
String prompt = "Guess a number between 1 and 10.";
while (true) {
try {
String guessString = ask(ui, prompt);
int guess = Integer.parseInt(guessString);
if (guess == number) {
ui.access(() -> Notification.show("Correct"));
return;
} else if (guess > number) {
prompt = "Too high. Guess again.";
} else {
prompt = "Too low. Guess again.";
}
} catch (NumberFormatException e) {
prompt = "Please enter a number.";
}
}
}).start();
}
private static String ask(UI ui, String prompt) {
CompletableFuture<String> future = new CompletableFuture<>();
ui.access(() -> ui.getPage().executeJs("return prompt($0)", prompt)
.then(String.class, future::complete));
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
(I know I’m doing things wrong with regards to e.g. starting threads and cacthing interrup exceptions - I’ve chosen the least amount of boilerplate for the parts that are not central to the question at hand.)
Thanks for the tip, I’ll give it at spin. At first glance I would have thought that it would just move the problem “one UI deeper” but if you really can block on future.get in sub-thread like that then it looks promising.
First of all, thanks a lot Leif for Your precious explanations.
However, I tried to follow Your suggestions, particularly the last one, but I couldn’t get a proper result: JS values are read properly, but not always on the current HTTP request-response cycle (sometimes I have to wait for the next one).
In other words, I avoided deadlocks, but still the result is retrieved “randomly” (sometimes at the right moment i.e. when leaving focus, sometimes “late” i.e. I have to wait the next HTTP request to “awaken” some locked threads, or something like that…).
Maybe you could add the blur listener on the client side since you are only checking something in javascript.
Something like that for onblur listener:
if (((itemsWidget == document.activeElement) || (newItemWidget == document.activeElement))) {
// call the server do_other_stuff
this.$.server.do_other_stuff();
}
It’s easier than add a blur listener to call the server, then call the client to check if you can call the server :).
But perhaps you didn’t put everything in your sample.
Hi Jean-Christophe, and thanks for taking the time to answer.
As I only wrote server-side code, how can I add client-side listeners? Do I have to write client-side components, or is it possible to play with com.vaadin.flow.dom.Element and “inject” the onblur JS event from there?
Just for the sake of curiosity, I still don’t understand why my original code doesn’t work, as it’s basically the same as Leif’s game reported some replies ago. Any hint? Because it’s driving me mad to not being able to get it…