Class Asyncc

java.lang.Object
org.ores.async.Asyncc

public class Asyncc extends Object
Entry point for async.java's combinators.

All public combinator methods are static. They share a common shape: take a collection of tasks (or values) and an error-first final callback. The final callback fires at most once, with either (err, null) if any task failed or (null, results) once all tasks have completed.

The combinator catalogue

Combinators and their shape
combinatorshapetypical use
Parallel fan out N tasks, await all independent async lookups that combine into one response
ParallelLimit parallel with concurrency cap many tasks, bounded in-flight (avoid downstream overload)
Series sequential tasks, collect each result step-by-step workflows where each step is independent
Waterfall each task receives the prior task's value typed pipelines (parse → validate → transform → persist)
Race first task to finish wins primary + replica lookups, timeout-via-task patterns
Times run the same task N times batch generators, repeat-with-different-id workflows
Each parallel "for each" with concurrency cap; no collected result fire-and-forget batches (e.g. send email per user)
Map / FilterMap / Reduce / GroupBy / Concat / Inject collection transforms with async element functions data pipelines where each element does I/O
Whilst / DoWhilst async loop with sync test polling, retry-until-success

The c convention

Examples in this Javadoc consistently name the continuation parameter c, short for continuation. A continuation is fired with either a successful value or an error; three equivalent ways to do that:

   c.done(null, value);   // canonical error-first form
   c.success(value);      // shorthand for done(null, value)     — v0.2.4+
   c.fail(err);           // shorthand for done(err, null)       — v0.2.4+
 

Minimal example

   List<Asyncc.AsyncTask<String, Throwable>> tasks = List.of(
       c -> exec.submit(() -> c.success(fetchA())),
       c -> exec.submit(() -> c.success(fetchB()))
   );

   Asyncc.Parallel(tasks, (err, results) -> {
       if (err != null) { handleError(err); return; }
       // results.get(0) and results.get(1) are in submission order
   });
 

Skipping the error-check boilerplate

For call sites that do not want to write the if (err != null) ... preamble, wrap a value-only consumer with WrapErrFirst.wrap(java.util.function.Consumer):

   import static org.ores.async.WrapErrFirst.wrap;

   Asyncc.Parallel(tasks, wrap(results -> {
       var scored = score(req, results.get(0), results.get(1));
       reply.send(serialize(scored));
   }));
 

wrap(onSuccess) throws on any unhandled error (preserving the original Throwable as the cause when available). For explicit error handling without the if/else preamble, use the two-arg form wrap(onSuccess, onError).

Error handling

The first task to call c.fail(err) (or c.done(err, null)) short-circuits the combinator: the final callback fires immediately with that error, and any later task completions are discarded (the at-most-once guard absorbs duplicate fires). This makes error handling local: you do not need to coordinate cancellation across siblings — just report the error.

   Asyncc.Parallel(tasks, (err, results) -> {
       if (err instanceof TimeoutException) { reply.timeout(); return; }
       if (err != null)                     { reply.error(err);  return; }
       reply.success(results);
   });
 

Composition (real-world example)

Combinators nest because they all honour the same callback shape. The pipeline below uses Waterfall, Map, Parallel, and Reduce together:

   // For each input URL: fetch the page, extract links, classify each link in parallel,
   // then aggregate per-domain counts.
   Asyncc.Map(urls, (url, perUrl) -> {
       Asyncc.Waterfall(List.of(
           c -> fetchPage(url, c),
           (html, c) -> c.success(extractLinks(html)),
           (links, c) -> Asyncc.Map(links, (link, inner) -> {
               Asyncc.Parallel(List.of(
                   c2 -> exec.submit(() -> c2.success(headOk(link))),
                   c2 -> exec.submit(() -> c2.success(classify(link)))
               ), inner);
           }, c)
       ), perUrl);
   }, (err, perUrlResults) -> {
       Asyncc.Reduce(perUrlResults, new HashMap<String, Integer>(), (acc, list, c) -> {
           list.forEach(pair -> acc.merge(pair.get(1).toString(), 1, Integer::sum));
           c.success(acc);
       }, (err2, totals) -> reply.send(totals));
   });
 

Concurrency contract (v0.2.x)

  • At-most-once final callback, even under concurrent task completion.
  • Result-slot visibility: per-index writes happen-before the atomic counter increment that releases the final callback (v0.2.2 fix).
  • Lost-update-free counters: CounterLimit uses AtomicInteger (v0.2.0 fix; pinned by CounterLimitRaceTest).
  • No duplicate fires: routed through NeoUtils.fireFinalCallback(org.ores.async.ShortCircuit, java.lang.Object, org.ores.async.NeoEachI.IEachCallback<E>).

See async-java.github.io/blog for benchmark results and design notes.

See Also: