class Promise
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: «caughtâ€helloâ€Â»
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 overâ€resultâ€Â»
# Interact with original Promise
my $after = Promise.in(2).then(-> $p { say $p.status; say '2 seconds are over!'; 'result' });
say $after.result;
# OUTPUT: «Keptâ€2 seconds are overâ€resultâ€Â»
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 Channels. 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.