This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 981616d polish wording
981616d is described below
commit 981616dc38f818d1e91ddd049a6eceff490cafa8
Author: Paul King <[email protected]>
AuthorDate: Tue Apr 7 17:32:23 2026 +1000
polish wording
---
site/src/site/blog/groovy-async-await.adoc | 80 ++++++++++++++++++++++++------
1 file changed, 65 insertions(+), 15 deletions(-)
diff --git a/site/src/site/blog/groovy-async-await.adoc
b/site/src/site/blog/groovy-async-await.adoc
index 79eb9aa..ffb40bc 100644
--- a/site/src/site/blog/groovy-async-await.adoc
+++ b/site/src/site/blog/groovy-async-await.adoc
@@ -59,17 +59,22 @@ Quest loadHeroQuest(String loginToken) {
Variables are declared at the point of use. The return value is obvious.
No callbacks, no lambdas, no chained combinators. The method is a
-regular method — the caller decides whether to run it asynchronously:
+regular method and called in the regular way:
[source,groovy]
----
-// Run asynchronously:
-def quest = await async { loadHeroQuest(token) }
-
-// Or call directly (blocking — fine on virtual threads):
+// Call directly (blocking — fine on virtual threads):
def quest = loadHeroQuest(token)
----
+The caller can choose to run it asynchronously if they want:
+
+[source,groovy]
+----
+// Or run asynchronously:
+def quest = await async { loadHeroQuest(token) }
+----
+
=== Exception handling — just `try`/`catch`
What about the `.exceptionally(e -> Quest.DEFAULT)` fallback from
@@ -106,7 +111,7 @@ def prepareBattle(heroId, visibleVillainId) {
var inventory = async { fetchInventory(heroId) }
var villain = async { fetchVillain(visibleVillainId) }
- var (s, inv, v) = await Awaitable.all(stats, inventory, villain)
+ var (s, inv, v) = await stats, inventory, villain
return new BattleScreen(s, inv, v)
}
----
@@ -169,6 +174,33 @@ result, ignoring individual failures. Like JavaScript's
(succeed or fail) without throwing. Returns an `AwaitResult` list
with `success`, `value`, and `error` fields.
+=== Combinator summary
+
+[cols="1,2,2,2", options="header"]
+|===
+| Combinator | Completes when | On failure | Use case
+
+| `Awaitable.all`
+| All succeed
+| Fails immediately on first failure (fail-fast)
+| Gather results from independent tasks
+
+| `Awaitable.allSettled`
+| All complete (success or fail)
+| Never throws; failures captured in `AwaitResult` list
+| Inspect every outcome, e.g. partial-success reporting
+
+| `Awaitable.any`
+| First task completes (success or failure)
+| Propagates the first completion's result or error
+| Latency-sensitive races, fastest-response wins
+
+| `Awaitable.first`
+| First task succeeds, or all fail
+| Throws only when every source fails (aggregate error)
+| Hedged requests, graceful degradation with fallbacks
+|===
+
== Generators and streaming
=== Dungeon waves — `yield return` and `for await`
@@ -189,21 +221,36 @@ def generateWaves(String dungeonId) {
}
def runDungeon(hero, dungeonId) {
- for await (wave in generateWaves(dungeonId)) {
+ for (wave in generateWaves(dungeonId)) {
wave.each { villain -> hero.fight(villain) }
}
}
----
-The producer yields each wave on demand. The consumer pulls with
-`for await`. Natural *back-pressure* — the producer blocks on each
+The producer yields each wave on demand. The consumer pulls with a normal for
loop.
+Natural *back-pressure* — the producer blocks on each
`yield return` until the consumer is ready. No queues, signals, or
synchronization.
-Since generators return a standard `Iterable`, regular `for` loops
-and Groovy collection methods (`collect`, `findAll`, `take`) also
-work — `for await` is optional for generators but required for
-reactive types (Flux, Observable).
+Since generators return a standard `Iterable`, regular `for` loops as shown
above
+and other Groovy collection methods (`collect`, `findAll`, `take`) also work.
+
+Other reactive libraries have different mechanisms for returning streaming
results.
+You can always use their native methods but Groovy's `for await` provides some
+syntactic sugar to make it more seamless:
+
+[source,groovy]
+----
+def runDungeon(hero, dungeonId) {
+ for await (wave in generateWaves(dungeonId)) {
+ wave.each { villain -> hero.fight(villain) }
+ }
+}
+----
+
+The consumer pulls with `for await` instead of `for` but no other changes are
required.
+You can optionally use `for await` with the builtin generators, but it's
required for
+other reactive types (Flux, Observable) if you want the Groovy async friendly
experience.
== Deferred cleanup — `defer`
@@ -418,8 +465,11 @@ Async/await targets sequential-looking code that is
actually
asynchronous, with language-level support for streams, cleanup,
structured concurrency, and framework bridging.
-Both approaches benefit from virtual threads on JDK 21+, and
-both can coexist in the same codebase.
+GPars' `callAsync()` and `asyncFun()` return futures that work
+naturally with `await` and the `Awaitable` combinators, so you
+can mix and match both styles in the same codebase.
+
+Both approaches benefit from virtual threads on JDK 21+.
== Conclusion