Search code examples
javagraalvm

How to wait using graalvm in java in js method in a api call?


is is possible to wait for an async function to resolve and get its output using graalvm?

why is result containing a bunch of extra stuff?

how do i get the returned value only?

code::

    public void main(  ) {

        try (Context ctx = Context.newBuilder("js")
                .allowAllAccess(true)
                .option("engine.WarnInterpreterOnly", "false")
                .build()) {

            String combinedScript = """
            // Define the async function
            async function dummyAsyncFunction() {
                console.log("dummyAsyncFunction()");
                // Simulate an asynchronous operation without setTimeout
                let start = new Date().getTime();
                while (new Date().getTime() - start < 2000) {
                    // Busy-wait for 1 second
                }
                // Return the message
                return "thanks for waiting";
            }
            
            // Call the async function and handle the returned value
            dummyAsyncFunction().then(message => {
                console.log(message); // Output: thanks for waiting
                return message;
            }).catch(e => {
                console.log("Error: " + e);
            });
        """;

            // Evaluate the combined script
            Value result = ctx.eval("js", combinedScript);
            System.out.println("result: "+result);

        } catch (Exception e) {
        }


    }

output::

 run_server_script()
dummyAsyncFunction()
thanks for waiting
result: Promise{[[PromiseStatus]]: "resolved", [[PromiseValue]]: "thanks for waiting"}

this is the only stackoverflow that i found that was somewhat relevant but it didnt help me::

GraalVM JavaScript in Java - How to identify an async method

GraalVM JavaScript in Java - How to identify an async method

the expected behavior is that result = "thanks for waiting"

UPDATE::

I can do something like this but it looks wrong but it works. it looks wrong because i have to parse the string for the data::

Value result = ctx.eval("js", combinedScript); System.out.println("str: " + result.toString() ); 
Pattern pattern = Pattern.compile("\[\[PromiseValue\]\]:\\s*\"([^\"]*)\""); 
Matcher matcher = pattern.matcher(result.toString()); 

output::

result: Promise{[[PromiseStatus]]: "resolved", [[PromiseValue]]: "thanks for waiting"} 

str: Promise{[[PromiseStatus]]: "resolved", [[PromiseValue]]: "thanks for waiting"} Extracted value: thanks for waiting 

Solution

  • calling then()

    The documentation on interopability shows you how to do this. You can call then() on the promise with a lambda expression.

    Value result = ctx.eval("js", combinedScript);
    Consumer<Object> finishedHandler = result -> System.out.println("result: " + result);
    result.invokeMember("then", finishedHandler);
    

    synchronous with CountDownLatch

    If you want to wait for the Promise, you could use CountDownLatch or similar:

    Value result = ctx.eval("js", combinedScript);
    CountDownLatch awaiter = new CountDownLatch(1);//awaiter.await() will wait until awaiter.countDown() is called
    var resultStore = new Object(){
        private Object result;//we will store the result here
    };
    Consumer<Object> finishedHandler = result -> {
        resultStore.result = result;
        awaiter.countDown();//when the Promise is resolved, the latch is opened
    };
    result.invokeMember("then", finishedHandler);
    awaiter.await();//wait until countDown() is executed i.e. until the promise is resolved
    System.out.println("result: " + resultStore.result);
    

    However, note that this would wait forever if the promise never resolves. You could prevent this by interrupting the thread calling await() (resulting in that method throwing an InterruptedException) or by calling await with a timeout.

    alternatively with BlockingQueue

    In addition to CountDownLatch, you could also use a BlockingQueue where you add the result when the promise is resolved.

    Value result = ctx.eval("js", combinedScript);
    BlockingQueue queue = new LinkedBlockingQueue();
    Consumer<Object> finishedHandler = queue::add;
    result.invokeMember("then", finishedHandler);
    Object result = queue.take();//waits until the result is added
    System.out.println("result: " + result);
    

    As with CountDownLatch, BlockingQueues allow interruption and provide a way to wait with a timeout using the poll method.

    Your JS code isn't asynchronous

    That being said, there is an issue with your JS code: You aren't doing anything asynchronously. If you have an async method that doesn't use any await, calling it would just take as long as the code takes to run and it would then return a resolved promise.

    I understand that your JS code is just an example but you should never do busy waiting in JS code. If you want to wait in JS, you probably want to do something like this.