Sterling has too many projects

Blogging about Raku programming, microcontrollers & electronics, 3D printing, and whatever else...

Promises

In Raku, Promises represent the simplest of the high-level language features for communicating between asynchronous tasks. They are very much like promises between people. For example, I might promise my son I will help him with his school work. I keep that promise when I help him. Or I break that promise if, for any reason, I fail to help him. The same is true of a Promise in Raku. A Promise to return a value is kept when that value arrives. A Promise to return a value is broken if an error occurs that prevents the value from arriving.

So, let’s see a basic promise in action in Raku:

my $promise = start {
    my $nth = 0;
    my $nth-prime;
    for 1..* -> $number {
        $nth++ if $number.is-prime;
        if $nth == 10_000 {
            $nth-prime = $number;
            last;
        }
    }
    $nth-prime;
}
await $promise.then({ say .result });

The code above uses a start block to begin finding the 10,000th prime. This block returns a Promise object. This object exists in one of three states (which you can check with the .status method). The initial state is Planned and then it enters either of two final states. The Broken state is entered if a result cannot be reached (usually because an exception occurred) and the Kept state is entered when the result becomes available. Once in the Kept status, the .result method will immediately return the value of the kept Promise.

Promises can be chained together using then .then method. This works by adding another block that starts as soon as the first is kept. The new block will be given the previous Promise object as an argument and the method returns a new Promise which will contain result from the following block.

The start block in the code above schedules the computation to run on the next unused thread in the default thread pool and returns a Promise object.1 We use .then() to output the .result of the computation as soon as it comes available.

Finally, we have the await statement, which causes the main thread to pause until the value becomes available. Without that statement, our program would end before the computation completes.

An await also allows a broken Promise to deliver exceptions. Consider this code:

my $promise = start { die 'bad stuff' }
sleep 1;
say 'something';

The code above will sleep for 1 second and print “something” to output. However, the exception will never be received. This is because while an exception causes the promise to be broken, we aren’t looking at the result of the Promise at all. We can add an await on the Promise where we are ready to receive the value and any exception thrown that causes the Promise to be broken will be received:

my $promise = start { die 'bad stuff' }
sleep 1;
say 'something';
await $promise;
CATCH {
    default {
        say "ERROR: $_.message()";
    }
}

This code does exactly the same thing as before, but also outputs “ERROR: bad stuff” after “something”. Always be sure to either handle your exceptions inside the start block or in another block that receives the Promise this way, or you may end up with bizarre and unexpected problems.

Those are the essential elements of Raku Promises.

Cheers.


  1. Note that this is the process for the usual default scheduler running in Rakudo under MoarVM. What actually happens might vary somewhat based on the current value in the $*SCHEDULER variable. I will discuss this further in a future post.  ↩