Promises and start
2026-03-19
Raku has first-class concurrency support built into the language. At the foundation of it all is the Promise, a value that will be resolved at some point in the future. Combined with start blocks and await, you can write concurrent code that reads almost like sequential code.
Creating Promises with start
The simplest way to create a promise is with a start block. This schedules code to run on a thread pool and immediately returns a Promise object:
my $promise = start {
sleep 2;
42
};
say $promise.status;
say await $promise;
The start block runs asynchronously. The value of the last expression in the block becomes the result of the promise.
Promise States
A promise goes through a lifecycle:
my $p = start { sleep 1; "done" };
say $p.status;
sleep 2;
say $p.status;
say $p.result;
The three states are:
- Planned -- the promise has not yet completed
- Kept -- the promise completed successfully
- Broken -- the promise threw an exception
my $broken = start { die "something went wrong" };
sleep 1;
say $broken.status;
try {
await $broken;
CATCH { default { say "Caught: {.message}" } }
}
Awaiting Multiple Promises
You can pass multiple promises to await and get all results at once:
my @promises = (1..5).map: -> $n {
start {
sleep $n * 0.5;
$n ** 2
}
};
my @results = await @promises;
say @results;
All five computations run concurrently. await blocks until every promise is kept (or one breaks).
Chaining with .then
Use .then to schedule work that depends on a previous promise:
my $chain = start { 10 }
.then({ .result * 2 })
.then({ .result + 5 })
.then({ say "Final: {.result}" });
await $chain;
Each .then block receives the previous promise as its argument. Call .result to get the value.
Promise.in -- Delayed Execution
Create a promise that is automatically kept after a delay:
my $delayed = Promise.in(3);
say "Waiting...";
await $delayed;
say "3 seconds have passed!";
This is useful for timeouts:
my $work = start { sleep 10; "done" };
my $timeout = Promise.in(2);
my $winner = await Promise.anyof($work, $timeout);
if $winner === $timeout {
say "Timed out!";
} else {
say "Completed: {$work.result}";
}
Promise.allof and Promise.anyof
These class methods let you combine promises:
my @tasks = (1..3).map: -> $n { start { sleep $n; say "Task $n done" } };
await Promise.allof(@tasks);
say "All tasks finished";
my $fast = start { sleep 1; "fast" };
my $slow = start { sleep 5; "slow" };
await Promise.anyof($fast, $slow);
say "First one done: {$fast.result}";
Keeping and Breaking Manually
You can create promises and resolve them manually using a Promise vow:
my $p = Promise.new;
my $vow = $p.vow;
start {
sleep 1;
$vow.keep("Here is your data");
};
say await $p;
To break a promise (signal an error):
my $p = Promise.new;
my $vow = $p.vow;
start {
$vow.break("Connection failed");
};
sleep 1;
try {
await $p;
CATCH { default { say "Error: {.message}" } }
}
Practical Example: Parallel File Processing
Here is a realistic example that processes multiple files concurrently:
my @files = dir('.', test => /\.txt$/);
my @promises = @files.map: -> $file {
start {
my $content = $file.slurp;
my $words = $content.words.elems;
"$file: $words words"
}
};
for await @promises -> $result {
say $result;
}
Error Handling Best Practices
Always handle potential errors when working with promises:
my @tasks = (1..5).map: -> $n {
start {
die "Bad number" if $n == 3;
$n * 10
}
};
for @tasks -> $task {
try {
say await $task;
CATCH { default { say "Failed: {.message}" } }
}
}
Promises give you a clean, composable foundation for concurrency in Raku. In future posts, we will explore Channels and Supplies, which build on promises to handle streams of data.