Skip to content

Commit

Permalink
feat: Wrap the JS snippet in async function (#18698)
Browse files Browse the repository at this point in the history
* Wrap the JS snippet in async function

Wondering if anything else would change for users but await would just magically start to work 馃 Let's see what the test say...

Quick prototype for #18693

* test: added simple IT test for async/await support

* use async function also for Page.executeJs

* update javadocs

* refactor test

---------

Co-authored-by: Marco Collovati <marco@vaadin.com>
  • Loading branch information
mstahv and mcollovati committed Feb 15, 2024
1 parent ca66faa commit a79fe8a
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,15 @@ public void addDynamicImport(String expression) {
/**
* Asynchronously runs the given JavaScript expression in the browser.
* <p>
* The returned <code>PendingJavaScriptResult</code> can be used to retrieve
* any <code>return</code> value from the JavaScript expression. If no
* return value handler is registered, the return value will be ignored.
* The expression is executed in an <code>async</code> JavaScript method, so
* you can utilize <code>await</code> syntax when consuming JavaScript API
* returning a <code>Promise</code>. The returned
* <code>PendingJavaScriptResult</code> can be used to retrieve the
* <code>return</code> value from the JavaScript expression. If a
* <code>Promise</code> is returned in the JavaScript expression,
* <code>PendingJavaScriptResult</code> will report the resolved value once
* it becomes available. If no return value handler is registered, the
* return value will be ignored.
* <p>
* The given parameters will be available to the expression as variables
* named <code>$0</code>, <code>$1</code>, and so on. Supported parameter
Expand Down
18 changes: 10 additions & 8 deletions flow-server/src/main/java/com/vaadin/flow/dom/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.vaadin.flow.dom;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -45,8 +44,6 @@
import com.vaadin.flow.internal.JsonCodec;
import com.vaadin.flow.internal.JsonUtils;
import com.vaadin.flow.internal.StateNode;
import com.vaadin.flow.internal.nodefeature.ElementData;
import com.vaadin.flow.internal.nodefeature.TextNodeMap;
import com.vaadin.flow.internal.nodefeature.VirtualChildrenList;
import com.vaadin.flow.server.AbstractStreamResource;
import com.vaadin.flow.server.Command;
Expand Down Expand Up @@ -1383,10 +1380,15 @@ public PendingJavaScriptResult callJsFunction(String functionName,
// When updating JavaDocs here, keep in sync with Page.executeJavaScript
/**
* Asynchronously runs the given JavaScript expression in the browser in the
* context of this element. The returned
* <code>PendingJavaScriptResult</code> can be used to retrieve any
* <code>return</code> value from the JavaScript expression. If no return
* value handler is registered, the return value will be ignored.
* context of this element. The expression is executed in an
* <code>async</code> JavaScript method, so you can utilize
* <code>await</code> syntax when consuming JavaScript API returning a
* <code>Promise</code>. The returned <code>PendingJavaScriptResult</code>
* can be used to retrieve the <code>return</code> value from the JavaScript
* expression. If a <code>Promise</code> is returned in the JavaScript
* expression, <code>PendingJavaScriptResult</code> will report the resolved
* value once it becomes available. If no return value handler is
* registered, the return value will be ignored.
* <p>
* This element will be available to the expression as <code>this</code>.
* The given parameters will be available as variables named
Expand Down Expand Up @@ -1426,7 +1428,7 @@ public PendingJavaScriptResult executeJs(String expression,
.concat(Stream.of(parameters), Stream.of(this));

// Wrap in a function that is applied with last parameter as "this"
String wrappedExpression = "return (function() { " + expression
String wrappedExpression = "return (async function() { " + expression
+ "}).apply($" + parameters.length + ")";

return scheduleJavaScriptInvocation(wrappedExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ private static JsonArray encodeExecuteJavaScript(
//@formatter:off
expression =
"try{"
+ "Promise.resolve((function(){"
+ "Promise.resolve((async function(){"
+ expression
+ "})()).then($"+successIndex+",function(error){$"+errorIndex+"(''+error)})"
+ "}catch(error){"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Input;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.internal.JsonUtils;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.uitest.servlet.ViewTestLayout;
Expand All @@ -28,6 +29,7 @@

@Route(value = "com.vaadin.flow.uitest.ui.ExecJavaScriptView", layout = ViewTestLayout.class)
public class ExecJavaScriptView extends AbstractDivView {

@Override
protected void onShow() {
NativeButton alertButton = createJsButton("Alert", "alertButton",
Expand All @@ -42,6 +44,43 @@ protected void onShow() {
"console.log($0)", JsonUtils.createArray(
Json.create("Hello world"), Json.create(true)));

NativeButton elementAwaitButton = createButton("Element await button",
"elementAwaitButton",
e -> e.getSource().getElement().executeJs("""
const result = new Promise((resolve) => {
setTimeout(() => resolve(42), 10);
});
return await result;
""").then(Integer.class, success -> {
Span span = new Span();
span.setId("elementAwaitResult");
span.setText("Element execute JS await result: " + success);
add(span);
}, error -> {
Span span = new Span();
span.setId("elementAwaitResult");
span.setText("Element execute JS await error: " + error);
add(span);
}));

NativeButton pageAwaitButton = createButton("Page await button",
"pageAwaitButton", e -> UI.getCurrent().getPage().executeJs("""
const result = new Promise((resolve) => {
setTimeout(() => resolve(72), 10);
});
return await result;
""").then(Integer.class, success -> {
Span span = new Span();
span.setId("pageAwaitResult");
span.setText("Page execute JS await result: " + success);
add(span);
}, error -> {
Span span = new Span();
span.setId("pageAwaitResult");
span.setText("Page execute JS await error: " + error);
add(span);
}));

NativeButton createElementButton = createButton(
"Create and update element", "createButton", e -> {
Input input = new Input();
Expand All @@ -51,7 +90,8 @@ protected void onShow() {
add(input);
});

add(alertButton, focusButton, swapText, logButton, createElementButton);
add(alertButton, focusButton, swapText, logButton, createElementButton,
elementAwaitButton, pageAwaitButton);
}

private NativeButton createJsButton(String text, String id, String script,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ public void testExecuteJavaScript() {
Assert.assertEquals("Value from js", findElement.getAttribute("value"));
}

@Test
public void testElementExecuteJavaScriptWithAwait() {
open();
getButton("elementAwaitButton").click();
WebElement result = waitUntil(
d -> findElement(By.id("elementAwaitResult")));
Assert.assertEquals("Element execute JS await result: 42",
result.getText());
}

@Test
public void testPageExecuteJavaScriptWithAwait() {
open();
getButton("pageAwaitButton").click();
WebElement result = waitUntil(
d -> findElement(By.id("pageAwaitResult")));
Assert.assertEquals("Page execute JS await result: 72",
result.getText());
}

private WebElement getButton(String id) {
return findElement(By.id(id));
}
Expand Down

0 comments on commit a79fe8a

Please sign in to comment.