[
https://issues.apache.org/jira/browse/GROOVY-9381?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18033368#comment-18033368
]
Jochen Theodorou commented on GROOVY-9381:
------------------------------------------
finally found some time to write something here again. So let me come back to
my points and talk about it in more detail.
h3. Return Values
{code:Java}
async int foo() {1}
assert await foo() == 1
assert foo().class == Promise.class
assert await foo() andthen foo() andThen foo() == 1
assert await foo() << foo() << foo() == 1
{code}
Base on that the return value will be wrapped in a Promise The code above would
cover my expectations. Maybe the line before the last line may needsome kind of
brackets, but that would be my expectation for a command expression based on a
andThen method on Promise, which executes the next promise after the inner
Promise did complete. And maybe we want something like << for operator
overloading for it... just thinking.
h3. Exception Handling
{code:Java}
try {
def user = await fetchUserData(1)
} catch (UserNotFoundException e) {
println "Caught error: ${e.message}"
}
{code}
is quite easy, of course with the usual problem of not having user anymore.
{code:Java}
try {
def user = await fetchUserData(1).or(fetchUserData(2))
} catch (UserNotFoundException e) {
println "Caught error: ${e.message}"
}
{code}
In this code I depend on a promise method called or, which will combine the
Promises to complete if any of the promises completes. Here my expectations are
that fetchUserData(1) is not called before the or has been executed and that if
fetchUserData(2) is faster (which it obviously is not here) its exception will
be thrown and a possible exception from fetchUserData(1) is ignored. You could
argue that this is an implementation detail of "or", but not starting
fetchUserData(1) immediatly is imho not... the await call starts the execution
of the promise.
{code:Java}
try {
def user = await fetchUserData(1).and(fetchUserData(2))
} catch (UserNotFoundException e) {
println "Caught error: ${e.message}"
}
{code}
Similar to "or" this "and" here executes fetchUserData(1) and fetchUserData(2)
in parallel, but this time waits for both to complete. the question here though
is... what exception do they throw? Again, strictly speaking an implementation
detail of "and", but this situation can happen and people need to know ow to
handle this.
h3. Using a special return type
{code:Java}
Promise<Integer> foo(){ return new MyPromise(1)}
await foo() == 1
{code}
Do we want to allow for code like this? Of course this means:
{code:Java}
async Promise<Integer> foo(){ return new MyPromise(1)}
await foo() instanceof MyPromise
await await foo() == 1
{code}
Again... just thinking out loud about options thins we may want to have or
maybe not. I feel not so sure about chaining await.... but it would make sense
here, would it not?
{code:Java}
def foo(){ return new MyPromise(1)}
await foo() == 1
{code}
And of course we have to think about the case of calling a method with await,
that is not async. Exception? Just execute while ignoring await? And actually
one more
{code:Java}
async foo(){1}
def f1 = foo()
assert await f1 == 1
assert await f1 == 1
{code}
Should it be possible to wait twice for the same Promise? Sure. But does it
execute foo() once or twice? Knowing the most likely implementation the answer
is once... but is that what we want?
h3. async for loop
What I was thinking about is described here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
h3. Virtual Threads vs Platform Threads and integrations
Well, well.. this topic is actually more complicated than I thought. If I just
take Virtual Threads, Platform Threads, Executors, Pools, Tasks, Job and all
that together I reach the following abstractions:
# some way to influence what is started
# how to start
And here I start to wonder if the Promise API in the PR is not too rich and is
already implying too much. See for example my andThen, and/or methods above.
Virtual Threads are cooperative, maybe running in the same Platform Thread and
as such cannot run truly in parallel in that case. Even if you only go with a
thread pool for Platform Threads, you cannot expect always Platform Thread
parallelism. And even if is makes sense to define a thread pool of a certain
size, depending on the number of cores... if everyone is doing that, then you
do not gain the results you wish for. So just creating a pool is ok for the
stand-alone case, but not if you are in a framework. And maybe the pool is not
about threads, but about a cluster.
h3. Back to Basics?
I think the real power of async-await becomes more clear when you think about
it with cooperative multitasking in mind. calling an async method will execute
that code in a different context, while my context is paused. Or maybe better
like this... Let us assume I have a cooperative scheduler, that will execute
code in a single thread. If I await a method call I enqueue the call in the
scheduler. And once the code completes it will schedule to continue where my
await left of. that gives a scheduler two slots to cooperatively execute other
code paths. In that sense virtual threads would be the nearest implementation
idea to this. But we are on the JVM, which is really good with Threads. Where
virtual threads come into play is when high load causes ten-thousands of
threads to spawn and the context switch starts to cost more than the executed
code.
I think what we need is the Promise in a basic API where it stores maybe a
bound MethodHandle as target, the exception and the return value, maybe a
status. The default implementation should consider to execute this code in
place, like synchronous code. And we need some kind of Scheduler/Executioner to
run them otherwise. and for that last part I was thinking that maybe await
should be a library method instead, that we can import with import static for
example. We have even support for "import static Foo.bar as await" allowing
await more as convention then as required method name.
Then I can put all the logic about virtual threads, platform threads, cluster
execution, scheduled execution, repeatable or not and so one in that
Implementation and provide the implementation that makes sense to us.
Where this of course fails is the async for loop idea....
Anyway... I spend hours writing this and I think it is most fair to let others
read and comment!
> Support async/await like ES7
> ----------------------------
>
> Key: GROOVY-9381
> URL: https://issues.apache.org/jira/browse/GROOVY-9381
> Project: Groovy
> Issue Type: New Feature
> Reporter: Daniel Sun
> Priority: Major
>
> Here is an example to show proposed syntax and backend API(Java's
> {{CompletableFuture}} or GPars's {{{}Promise{}}}), but I think it's better
> for Groovy to have its own {{Promise}} to decouple with Java API because
> async/await as a language feature should be as stable as possible.
> {{async}} will generate the {{Awaitable}} instance such as Groovy {{Promise}}
> implementing the {{Awaitable}} interface, and {{await}} can wait for any
> {{Awaitable}} instance to complete and unwrap it for the result.
> {code:java}
> /**
> * 1. An async function that simulates a network API call.
> * The 'async' keyword implies it runs asynchronously without blocking.
> */
> async fetchUserData(userId) {
> println "Starting to fetch data for user ${userId}..."
>
> // Simulate a 1-second network delay.
> Thread.sleep(1000)
>
> println "Fetch successful!"
> // The 'async' function implicitly returns a "CompletableFuture" or
> "Promise" containing this value.
> return [userId: userId, name: 'Daniel']
> }
> /**
> * 2. An async function that uses 'await' to consume the result.
> */
> async processUserData() {
> println "Process started, preparing to fetch user data..."
>
> try {
> // 'await' pauses this function until fetchUserData completes
> // and returns the final result directly.
> def user = await fetchUserData(1)
>
> println "Data received: ${user}"
> return "Processing complete for ${user.name}."
>
> } catch (Exception e) {
> return "An error occurred: ${e.message}"
> }
> }
> // --- Execution ---
> println "Script starting..."
> // Kick off the entire asynchronous process.
> def future = processUserData()
> // This line executes immediately, proving the process is non-blocking.
> println "Script continues to run while user data is being fetched in the
> background..."
> def result = future.get()
> println "Script finished: ${result}"
> {code}
> Use async/await with closure or lambda expression:
> {code}
> // use closure
> def c = async {
> println "Process started, preparing to fetch user data..."
>
> try {
> // 'await' pauses this function until fetchUserData completes
> // and returns the final result directly.
> def user = await fetchUserData(1)
>
> println "Data received: ${user}"
> return "Processing complete for ${user.name}."
>
> } catch (Exception e) {
> return "An error occurred: ${e.message}"
> }
> }
> def future = c()
> {code}
> {code}
> // use lambda expression
> def c = async () -> {
> println "Process started, preparing to fetch user data..."
>
> try {
> // 'await' pauses this function until fetchUserData completes
> // and returns the final result directly.
> def user = await fetchUserData(1)
>
> println "Data received: ${user}"
> return "Processing complete for ${user.name}."
>
> } catch (Exception e) {
> return "An error occurred: ${e.message}"
> }
> }
> def future = c()
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)