flow icon indicating copy to clipboard operation
flow copied to clipboard

Element.executeJs() silently swallows any JavaScript errors

Open mvysny opened this issue 1 year ago • 8 comments

Description of the bug

Calling getElement().executeJs("someFunnyFunctionThatDoesntExist()") from server-side will silently fail - the JS ReferenceError is not logged in server-side nor client-side.

I can see that the information about the failure is sent to the server-side, since the XHR message contains the following:

Sending xhr message to server: {"csrfToken":"456710bb-4724-4d05-8686-97f674488d18","rpc":[{"type":"channel","node":6,"channel":23,"args":["ReferenceError: someFunnyFunctionThatDoesntExist is not defined"]}],"syncId":24,"clientId":24} [FlowClient-d5d5e377.js:1:8091](http://localhost:8080/VAADIN/build/FlowClient-d5d5e377.js)

However, nothing is logged on the server-side.

Expected behavior

Something should be logged as WARN/ERROR, either in client-side or in server-side. Even better, an exception could be thrown in the server-side.

Minimal reproducible example

@Route("")
public class MainView extends VerticalLayout {

    public MainView() {
        add(new Button("Press me", e -> {
            getElement().executeJs("someFunnyFunctionThatDoesntExist()")
                    .then((SerializableConsumer<JsonValue>) jsonValue -> Notification.show("Never gets called"));
        }));
    }
}

Versions

  • Vaadin / Flow version: 24.1.5
  • Java version: 17

mvysny avatar Aug 23 '23 08:08 mvysny

That's because you're (accidentally) explicitly suppressing the error.

If you don't have any .then on the execution, then there will be an error message in the browser console.

Screenshot 2023-08-23 at 12 54 54

If you have a .then, then the exception will be passed to the server and delivered to the second optional parameter of .then. If no such parameter is passed, then the error is silently ignored and that's probably something that we should look into addressing.

getElement().executeJs("someFunnyFunctionThatDoesntExist()").then(
		ignore -> Notification.show("Never gets called"),
		ignore -> Notification.show("An error occurred"));

Legioth avatar Aug 23 '23 09:08 Legioth

So your example code is really akin to this native Java example that also silently ignores the exception:

CompletableFuture.supplyAsync(() -> {
	throw new RuntimeException();
}).thenAccept(value -> System.out.println("This never gets called"));

Legioth avatar Aug 23 '23 09:08 Legioth

IMO we can try add a default handler when the seconds handler (custom error handler) is not given to executeJs. I don't know if it would be possible to switch between the default one and custom one. If not, we can at lest document this behaviour in javadoc and on online docs.

mshabarov avatar Aug 29 '23 11:08 mshabarov

Currently, if an error handler is not provided, the behavior is to log the error message at debug level (PendingJavaScriptInvocation category). What we do not log when the error handler is not given is the cancellation case.

mcollovati avatar Jan 26 '24 10:01 mcollovati

It would be good to document how error handling works here and here

mcollovati avatar Jan 26 '24 11:01 mcollovati

I'd definitely suggest to use a default handler that logs an exception stacktrace into slf4j. That will help the programmer tremendously: he can quickly find where the offending javascript is executed, and can quickly fix the code in Java.

mvysny avatar Feb 22 '24 08:02 mvysny

@mvysny PendingJavaScriptInvocation already logs errors at debug level when there is no custom error handler. Are you talking about a different handler? O the suggestion is to change the level from DEBUG to WARN or ERROR?

mcollovati avatar Feb 22 '24 08:02 mcollovati

Yup, exactly. By default my log level for Vaadin is set to INFO or WARN (so that my log is not cluttered), causing the errors to be not logged. Logging them as WARN or ERROR would fix things for me.

mvysny avatar Feb 22 '24 08:02 mvysny