my enum PromiseStatus (:Planned(0), :Kept(1), :Broken(2)); class Promise {}
A Promise
is used to handle the result of a computation that might not have finished. It allows the user to execute code once the computation is done (with the then
method), execution after a time delay (with in
), combining promises, and waiting for results.
my $p = Promise.start({ sleep 2; 42}); $p.then({ say .result }); # will print 42 once the block finished say $p.status; # OUTPUT: «Planned» $p.result; # waits for the computation to finish say $p.status; # OUTPUT: «Kept»
There are two typical scenarios for using promises. The first is to use a factory method (start
, in
, at
, anyof
, allof
, kept
, broken
) on the type object; those will make sure that the promise is automatically kept or broken for you, and you can't call break
or keep
on these promises yourself.
The second is to create your promises yourself with Promise.new
. If you want to ensure that only your code can keep or break the promise, you can use the vow
method to get a unique handle, and call keep
or break
on it:
sub async-get-with-promise($user-agent, $url) { my $p = Promise.new; my $v = $p.vow; # do an asynchronous call on a fictive user agent, # and return the promise: $user-agent.async-get($url, on-error => -> $error { $v.break($error); }, on-success => -> $response { $v.keep($response); } ); return $p; }
Further examples can be found in the concurrency page.
Methods§
method start§
method start(Promise:U: &code, :$scheduler = $*SCHEDULER --> Promise:D)
Creates a new Promise that runs the given code object. The promise will be kept when the code terminates normally, or broken if it throws an exception. The return value or exception can be inspected with the result
method.
The scheduler that handles this promise can be passed as a named argument.
There is also a statement prefix start
that provides syntactic sugar for this method:
# these two are equivalent: my $p1 = Promise.start({ ;#`( do something here ) }); my $p2 = start { ;#`( do something here ) };
As of the 6.d version of the language, start
statement prefix used in sink context will automatically attach an exceptions handler. If an exception occurs in the given code, it will be printed and the program will then exit, like if it were thrown without any start
statement prefixes involved.
use v6.c; start { die }; sleep ⅓; say "hello"; # OUTPUT: «hello»
use v6.d; start { die }; sleep ⅓; say "hello"; # OUTPUT: # Unhandled exception in code scheduled on thread 4 # Died # in block at -e line 1
If you wish to avoid this behavior, use start
in non-sink context or catch the exception yourself:
# Don't sink it: my $ = start { die }; sleep ⅓; say "hello"; # OUTPUT: «hello» # Catch yourself: start { die; CATCH { default { say "caught" } } }; sleep ⅓; say "hello"; # OUTPUT: «caughthello»
This behavior exists only syntactically, by using an alternate .sink
method for Promise
objects created by start
blocks in sink context, thus simply sinking a Promise
object that was created by other means won't trigger this behavior.
method in§
method in(Promise:U: $seconds, :$scheduler = $*SCHEDULER --> Promise:D)
Creates a new Promise that will be kept in $seconds
seconds, or later.
my $proc = Proc::Async.new('raku', '-e', 'sleep 10; warn "end"'); my $result = await Promise.anyof( my $promise = $proc.start, # may or may not work in time Promise.in(5).then: { # fires after 5 seconds no matter what unless $promise { # don't do anything if we were successful note 'timeout'; $proc.kill; } } ).then: { $promise.result } # OUTPUT: «timeout»
$seconds
can be fractional or negative. Negative values are treated as 0
(i.e. keeping the returned Promise
right away).
Please note that situations like these are often more clearly handled with a react and whenever block.
method at§
method at(Promise:U: $at, :$scheduler = $*SCHEDULER --> Promise:D)
Creates a new Promise
that will be kept $at
the given time—which is given as an Instant
or equivalent Numeric
—or as soon as possible after it.
my $p = Promise.at(now + 2).then({ say "2 seconds later" }); # do other stuff here await $p; # wait here until the 2 seconds are over
If the given time is in the past, it will be treated as now (i.e. keeping the returned Promise
right away).
Please note that situations like these are often more clearly handled with a react and whenever block.
method kept§
multi method kept(Promise:U: \result = True --> Promise:D)
Returns a new promise that is already kept, either with the given value, or with the default value True
.
method broken§
multi method broken(Promise:U: --> Promise:D) multi method broken(Promise:U: \exception --> Promise:D)
Returns a new promise that is already broken, either with the given value, or with the default value X::AdHoc.new(payload => "Died")
method allof§
method allof(Promise:U: *@promises --> Promise:D)
Returns a new promise that will be kept when all the promises passed as arguments are kept or broken. The result of the individual Promises is not reflected in the result of the returned promise: it simply indicates that all the promises have been completed in some way. If the results of the individual promises are important then they should be inspected after the allof
promise is kept.
In the following requesting the result
of a broken promise will cause the original Exception to be thrown. (You may need to run it several times to see the exception.)
my @promises; for 1..5 -> $t { push @promises, start { sleep $t; }; } my $all-done = Promise.allof(@promises); await $all-done; @promises>>.result; say "Promises kept so we get to live another day!";
method anyof§
method anyof(Promise:U: *@promises --> Promise:D)
Returns a new promise that will be kept as soon as any of the promises passed as arguments is kept or broken. The result of the completed Promise is not reflected in the result of the returned promise which will always be Kept.
You can use this to wait at most a number of seconds for a promise:
my $timeout = 5; await Promise.anyof( Promise.in($timeout), start { # do a potentially long-running calculation here }, );
method then§
method then(Promise:D: &code)
Schedules a piece of code to be run after the invocant has been kept or broken, and returns a new promise for this computation. In other words, creates a chained promise. The Promise
is passed as an argument to the &code
.
# Use code only my $timer = Promise.in(2); my $after = $timer.then({ say '2 seconds are over!'; 'result' }); say $after.result; # OUTPUT: «2 seconds are overresult» # Interact with original Promise my $after = Promise.in(2).then(-> $p { say $p.status; say '2 seconds are over!'; 'result' }); say $after.result; # OUTPUT: «Kept2 seconds are overresult»
method keep§
multi method keep(Promise:D: \result = True)
Keeps a promise, optionally setting the result. If no result is passed, the result will be True
.
Throws an exception of type X::Promise::Vowed
if a vow has already been taken. See method vow
for more information.
my $p = Promise.new; if Bool.pick { $p.keep; } else { $p.break; }
method break§
multi method break(Promise:D: \cause = False)
Breaks a promise, optionally setting the cause. If no cause is passed, the cause will be False
.
Throws an exception of type X::Promise::Vowed
if a vow has already been taken. See method vow
for more information.
my $p = Promise.new; $p.break('sorry'); say $p.status; # OUTPUT: «Broken» say $p.cause; # OUTPUT: «sorry»
method result§
method result(Promise:D:)
Waits for the promise to be kept or broken. If it is kept, returns the result; otherwise throws the result as an exception.
method cause§
method cause(Promise:D:)
If the promise was broken, returns the result (or exception). Otherwise, throws an exception of type X::Promise::CauseOnlyValidOnBroken
.
method Bool§
multi method Bool(Promise:D:)
Returns True
for a kept or broken promise, and False
for one in state Planned
.
method status§
method status(Promise:D --> PromiseStatus)
Returns the current state of the promise: Kept
, Broken
or Planned
:
say "promise got Kept" if $promise.status ~~ Kept;
method scheduler§
method scheduler(Promise:D:)
Returns the scheduler that manages the promise.
method vow§
my class Vow { has Promise $.promise; method keep() { ... } method break() { ... } } method vow(Promise:D: --> Vow:D)
Returns an object that holds the sole authority over keeping or breaking a promise. Calling keep
or break
on a promise that has vow taken throws an exception of type X::Promise::Vowed
.
my $p = Promise.new; my $vow = $p.vow; $vow.keep($p); say $p.status; # OUTPUT: «Kept»
method Supply§
method Supply(Promise:D:)
Returns a Supply
that will emit the result
of the Promise
being Kept or quit
with the cause
if the Promise
is Broken.
sub await§
multi await(Promise:D --> Promise) multi await(*@ --> Array)
Waits until one or more promises are all fulfilled, and then returns their values. Also works on Channel
s. Any broken promises will rethrow their exceptions. If a list is passed it will return a list containing the results of awaiting each item in turn.