/
Promise.pod6
335 lines (236 loc) · 10.2 KB
/
Promise.pod6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
=begin pod
=TITLE class Promise
=SUBTITLE Status/result of an asynchronous computation
my enum PromiseStatus (:Planned(0), :Kept(1), :Broken(2));
class Promise {}
A I<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 C<then> method), execution after a time delay (with C<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 (C<start>, C<in>, C<at>, C<anyof>, C<allof>, C<kept>, C<broken>)
on the type object; those will make sure that the promise is automatically kept
or broken for you, and you can't call C<break> or C<keep> on these promises
yourself.
The second is to create your promises yourself with C<Promise.new>. If you
want to ensure that only your code can keep or break the promise, you can use
the C<vow> method to get a unique handle, and call C<keep> or C<break> on it:
=begin code
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;
}
=end code
Further examples can be found in the L<concurrency page|/language/concurrency#Promises>.
=head1 Methods
=head2 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 C<result> method.
The scheduler that handles this promise can be passed as a named argument.
There is also a statement prefix C<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 6.d language, C<start> statement prefix used in L<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 C<start> statement prefixes involved.
=begin code
use v6.c;
start { die }; sleep ⅓; say "hello"; # OUTPUT: «hello»
=end code
=begin code
use v6.d.PREVIEW;
start { die }; sleep ⅓; say "hello";
# OUTPUT: Died
# in block at -e line 1
=end code
If you wish to avoid this behaviour, use C<start> in non-sink context or
catch the exception yourself:
=begin code
# 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»
=end code
This behaviour exists only syntaxically and not as part of method L<sink>
on L<Promise> object, thus sinking a L<Promise> object or having a C<start>
block as return value of a routine won't trigger this behaviour.
=head2 method in
method in(Promise:U: $seconds, :$scheduler = $*SCHEDULER --> Promise:D)
Creates a new Promise that will be kept in C<$seconds> seconds, or later.
my $proc = Proc::Async.new('perl6', '-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»
C<$seconds> can be fractional or negative. Negative values are treated as
C<0> (i.e. L<keeping|/routine/keep> the returned L<Promise> right away).
Please note that situations like these are often more clearly handled with
a L<react and whenever block|/language/concurrency#index-entry-react-react>.
=head2 method at
method at(Promise:U: $at, :$scheduler = $*SCHEDULER --> Promise:D)
Creates a new C<Promise> that will be kept C<$at> the given time—which is
given as an L<Instant> or equivalent L<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 L<now> (i.e.
L<keeping|/routine/keep> the returned L<Promise> right away).
Please note that situations like these are often more clearly handled with
a L<react and whenever block|/language/concurrency#index-entry-react-react>.
=head2 method kept
multi method kept(Promise:U: --> Promise:D)
multi method kept(Promise:U: \result --> Promise:D)
Returns a new promise that is already kept, either with the given value,
or with the default value True.
=head2 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 "Died".
=head2 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 C<allof> promise is kept.
In the following requesting the C<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!";
=head2 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
},
);
=head2 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.
my $timer = Promise.in(2);
my $after = $timer.then({ say "2 seconds are over!"; 'result' });
say $after.result; # 2 seconds are over
# result
=head2 method keep
multi method keep(Promise:D:)
multi method keep(Promise:D: \result)
Keeps a promise, optionally setting the result. If no result is passed, the
result will be C<True>.
Throws an exception of type C<X::Promise::Vowed> if a vow has already been
taken. See method C<vow> for more information.
my $p = Promise.new;
if Bool.pick {
$p.keep;
}
else {
$p.break;
}
=head2 method break
multi method break(Promise:D:)
multi method break(Promise:D: \cause)
Breaks a promise, optionally setting the cause. If no cause is passed, the
cause will be C<False>.
Throws an exception of type C<X::Promise::Vowed> if a vow has already been
taken. See method C<vow> for more information.
my $p = Promise.new;
$p.break('sorry');
say $p.status; # OUTPUT: «Broken»
say $p.cause; # OUTPUT: «sorry»
=head2 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.
=head2 method cause
method cause(Promise:D)
If the promise was broken, returns the result (or exception). Otherwise, throws
an exception of type C<X::Promise::CauseOnlyValidOnBroken>.
=head2 method Bool
multi method Bool(Promise:D:)
Returns C<True> for a kept or broken promise, and C<False> for one in state
C<Planned>.
=head2 method status
method status(Promise:D --> PromiseStatus)
Returns the current state of the promise: C<Kept>, C<Broken> or C<Planned>:
=for code :skip-test
say "promise got Kept" if $promise.status ~~ Kept;
=head2 method scheduler
method scheduler(Promise:D:)
Returns the scheduler that manages the promise.
=head2 method vow
=for code
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 C<keep> or C<break> on a promise that has vow taken throws an
exception of type C<X::Promise::Vowed>.
my $p = Promise.new;
my $vow = $p.vow;
$vow.keep($p);
say $p.status; # OUTPUT: «Kept»
=head2 method Supply
method Supply(Promise:D:)
Returns a L<Supply> that will emit the C<result> of the L<Promise> being Kept
or C<quit> with the C<cause> if the L<Promise> is Broken.
=head2 sub await
multi sub await(Promise:D --> Promise)
multi sub await(*@ --> Array)
Waits until one or more promises are I<all> fulfilled, and then returns their
values. Also works on L<channels|/type/Channel>. 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.
=end pod
# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6