Skip to content

Commit 82f5dcb

Browse files
committed
feat: Add Promise combinators for parallel async execution
Implements JavaScript-style Promise combinators for the Zyntax async runtime: - PromiseAll: Wait for all promises to complete (like Promise.all()) - Fast-fails on first error - Returns results in order - Supports timeout and manual polling - PromiseRace: Return first promise to complete (like Promise.race()) - Returns winner index and value - Supports timeout and manual polling - PromiseAllSettled: Collect all results regardless of success/failure - Returns SettledResult::Fulfilled or SettledResult::Rejected for each - Never fails, always waits for all promises All combinators support: - await_all() / await_first() for blocking execution - await_*_with_timeout() for deadline-based waiting - poll() for non-blocking manual polling - poll_count() for monitoring progress Added comprehensive tests: - test_promise_all_multiple_async_functions - test_promise_all_different_functions - test_promise_all_with_long_running_functions - test_promise_race_first_wins - test_promise_all_settled - test_promise_all_with_nested_await - test_promise_all_empty - test_promise_all_single Updated documentation with examples for all combinators.
1 parent 3262a02 commit 82f5dcb

4 files changed

Lines changed: 1043 additions & 10 deletions

File tree

book/13-async-runtime.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,160 @@ while promise.is_pending() {
478478
assert_eq!(promise.state(), PromiseState::Ready(ZyntaxValue::Int(30)));
479479
```
480480

481+
## Promise Combinators (Parallel Execution)
482+
483+
Zyntax provides JavaScript-style Promise combinators for running multiple async operations in parallel.
484+
485+
### PromiseAll - Wait for All
486+
487+
`PromiseAll` waits for all promises to complete, similar to JavaScript's `Promise.all()`:
488+
489+
```rust
490+
use zyntax_embed::{ZyntaxRuntime, ZyntaxValue, PromiseAll};
491+
492+
// Define multiple async functions
493+
runtime.load_module("zig", r#"
494+
async fn compute(x: i32) i32 {
495+
return x * 2;
496+
}
497+
498+
async fn sum_range(n: i32) i32 {
499+
var total: i32 = 0;
500+
var i: i32 = 1;
501+
while (i <= n) {
502+
total = total + i;
503+
i = i + 1;
504+
}
505+
return total;
506+
}
507+
"#)?;
508+
509+
// Create multiple promises
510+
let promises = vec![
511+
runtime.call_async("compute", &[ZyntaxValue::Int(5)])?, // 10
512+
runtime.call_async("compute", &[ZyntaxValue::Int(10)])?, // 20
513+
runtime.call_async("sum_range", &[ZyntaxValue::Int(100)])?, // 5050
514+
];
515+
516+
// Wait for all to complete
517+
let mut all = PromiseAll::new(promises);
518+
let results = all.await_all()?;
519+
520+
// results = [Int(10), Int(20), Int(5050)]
521+
println!("All completed after {} polls", all.poll_count());
522+
```
523+
524+
If any promise fails, `PromiseAll` returns the first error immediately (fast-fail).
525+
526+
### PromiseRace - First to Complete
527+
528+
`PromiseRace` resolves as soon as any promise completes, similar to `Promise.race()`:
529+
530+
```rust
531+
use zyntax_embed::{ZyntaxRuntime, ZyntaxValue, PromiseRace};
532+
533+
runtime.load_module("zig", r#"
534+
async fn quick(x: i32) i32 {
535+
return x * 2;
536+
}
537+
538+
async fn slow(n: i32) i32 {
539+
var total: i32 = 0;
540+
var i: i32 = 1;
541+
while (i <= n) {
542+
total = total + i;
543+
i = i + 1;
544+
}
545+
return total;
546+
}
547+
"#)?;
548+
549+
let promises = vec![
550+
runtime.call_async("slow", &[ZyntaxValue::Int(1000)])?, // Takes many polls
551+
runtime.call_async("quick", &[ZyntaxValue::Int(21)])?, // Completes quickly
552+
];
553+
554+
let mut race = PromiseRace::new(promises);
555+
let (winner_index, value) = race.await_first()?;
556+
557+
// winner_index = index of first promise to complete
558+
// value = result from that promise
559+
println!("Promise {} won with {:?}", winner_index, value);
560+
```
561+
562+
### PromiseAllSettled - Collect All Results
563+
564+
`PromiseAllSettled` waits for all promises regardless of success or failure:
565+
566+
```rust
567+
use zyntax_embed::{ZyntaxRuntime, ZyntaxValue, PromiseAllSettled, SettledResult};
568+
569+
let promises = vec![
570+
runtime.call_async("compute", &[ZyntaxValue::Int(1)])?,
571+
runtime.call_async("compute", &[ZyntaxValue::Int(2)])?,
572+
runtime.call_async("compute", &[ZyntaxValue::Int(3)])?,
573+
];
574+
575+
let mut settled = PromiseAllSettled::new(promises);
576+
let results = settled.await_all();
577+
578+
for (i, result) in results.iter().enumerate() {
579+
match result {
580+
SettledResult::Fulfilled(value) => println!("Promise {} succeeded: {:?}", i, value),
581+
SettledResult::Rejected(error) => println!("Promise {} failed: {}", i, error),
582+
}
583+
}
584+
```
585+
586+
### Timeout Support
587+
588+
All combinators support timeout-based waiting:
589+
590+
```rust
591+
use std::time::Duration;
592+
593+
// PromiseAll with timeout
594+
let mut all = PromiseAll::new(promises);
595+
match all.await_all_with_timeout(Duration::from_secs(5)) {
596+
Ok(results) => println!("All completed: {:?}", results),
597+
Err(e) => println!("Timeout or error: {}", e),
598+
}
599+
600+
// PromiseRace with timeout
601+
let mut race = PromiseRace::new(promises);
602+
match race.await_first_with_timeout(Duration::from_secs(1)) {
603+
Ok((index, value)) => println!("Winner: {} = {:?}", index, value),
604+
Err(e) => println!("Timeout or error: {}", e),
605+
}
606+
```
607+
608+
### Manual Polling for Combinators
609+
610+
For non-blocking execution, poll the combinator manually:
611+
612+
```rust
613+
use zyntax_embed::{PromiseAll, PromiseAllState};
614+
615+
let mut all = PromiseAll::new(promises);
616+
617+
loop {
618+
match all.poll() {
619+
PromiseAllState::Pending => {
620+
// Do other work while waiting
621+
process_other_events();
622+
}
623+
PromiseAllState::AllReady(values) => {
624+
println!("All {} promises completed!", values.len());
625+
break;
626+
}
627+
PromiseAllState::Failed(error) => {
628+
println!("A promise failed: {}", error);
629+
break;
630+
}
631+
}
632+
}
633+
```
634+
481635
## Async Computation Pipeline (Legacy Example)
482636

483637
```rust

crates/zyntax_embed/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ pub use runtime::{
7777
NativeType, NativeSignature,
7878
// Async ABI types
7979
AsyncPollResult,
80+
// Promise combinators (Promise.all, Promise.race, etc.)
81+
PromiseAll, PromiseAllState,
82+
PromiseRace, PromiseRaceState,
83+
PromiseAllSettled, SettledResult,
8084
};
8185
pub use grammar::{LanguageGrammar, GrammarError, GrammarResult};
8286

0 commit comments

Comments
 (0)