148
148
* });
149
149
* ```
150
150
*
151
+ * Promises returned by `async()` can be cancelled, and when done any currently
152
+ * and future awaited promise inside that and any nested fibers with their
153
+ * awaited promises will also be cancelled. As such the following example will
154
+ * only output `ab` as the [`sleep()`](https://reactphp.org/promise-timer/#sleep)
155
+ * between `a` and `b` is cancelled throwing a timeout exception that bubbles up
156
+ * through the fibers ultimately to the end user through the [`await()`](#await)
157
+ * on the last line of the example.
158
+ *
159
+ * ```php
160
+ * $promise = async(static function (): int {
161
+ * echo 'a';
162
+ * await(async(static function(): void {
163
+ * echo 'b';
164
+ * await(sleep(2));
165
+ * echo 'c';
166
+ * })());
167
+ * echo 'd';
168
+ *
169
+ * return time();
170
+ * })();
171
+ *
172
+ * $promise->cancel();
173
+ * await($promise);
174
+ * ```
175
+ *
151
176
* @param callable(mixed ...$args):mixed $function
152
177
* @return callable(): PromiseInterface<mixed>
153
178
* @since 4.0.0
154
179
* @see coroutine()
155
180
*/
156
181
function async (callable $ function ): callable
157
182
{
158
- return static fn (mixed ...$ args ): PromiseInterface => new Promise (function (callable $ resolve , callable $ reject ) use ($ function , $ args ): void {
159
- $ fiber = new \Fiber (function () use ($ resolve , $ reject , $ function , $ args ): void {
160
- try {
161
- $ resolve ($ function (...$ args ));
162
- } catch (\Throwable $ exception ) {
163
- $ reject ($ exception );
183
+ return static function (mixed ...$ args ) use ($ function ): PromiseInterface {
184
+ $ fiber = null ;
185
+ $ promise = new Promise (function (callable $ resolve , callable $ reject ) use ($ function , $ args , &$ fiber ): void {
186
+ $ fiber = new \Fiber (function () use ($ resolve , $ reject , $ function , $ args , &$ fiber ): void {
187
+ try {
188
+ $ resolve ($ function (...$ args ));
189
+ } catch (\Throwable $ exception ) {
190
+ $ reject ($ exception );
191
+ } finally {
192
+ FiberMap::unregister ($ fiber );
193
+ }
194
+ });
195
+
196
+ FiberMap::register ($ fiber );
197
+
198
+ $ fiber ->start ();
199
+ }, function () use (&$ fiber ): void {
200
+ FiberMap::cancel ($ fiber );
201
+ $ promise = FiberMap::getPromise ($ fiber );
202
+ if ($ promise instanceof CancellablePromiseInterface) {
203
+ $ promise ->cancel ();
164
204
}
165
205
});
166
206
167
- $ fiber ->start ();
168
- });
207
+ $ lowLevelFiber = \Fiber::getCurrent ();
208
+ if ($ lowLevelFiber !== null ) {
209
+ FiberMap::setPromise ($ lowLevelFiber , $ promise );
210
+ }
211
+
212
+ return $ promise ;
213
+ };
169
214
}
170
215
171
216
@@ -230,9 +275,18 @@ function await(PromiseInterface $promise): mixed
230
275
$ rejected = false ;
231
276
$ resolvedValue = null ;
232
277
$ rejectedThrowable = null ;
278
+ $ lowLevelFiber = \Fiber::getCurrent ();
279
+
280
+ if ($ lowLevelFiber !== null && FiberMap::isCancelled ($ lowLevelFiber ) && $ promise instanceof CancellablePromiseInterface) {
281
+ $ promise ->cancel ();
282
+ }
233
283
234
284
$ promise ->then (
235
- function (mixed $ value ) use (&$ resolved , &$ resolvedValue , &$ fiber ): void {
285
+ function (mixed $ value ) use (&$ resolved , &$ resolvedValue , &$ fiber , $ lowLevelFiber , $ promise ): void {
286
+ if ($ lowLevelFiber !== null ) {
287
+ FiberMap::unsetPromise ($ lowLevelFiber , $ promise );
288
+ }
289
+
236
290
if ($ fiber === null ) {
237
291
$ resolved = true ;
238
292
$ resolvedValue = $ value ;
@@ -241,7 +295,11 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber): void {
241
295
242
296
$ fiber ->resume ($ value );
243
297
},
244
- function (mixed $ throwable ) use (&$ rejected , &$ rejectedThrowable , &$ fiber ): void {
298
+ function (mixed $ throwable ) use (&$ rejected , &$ rejectedThrowable , &$ fiber , $ lowLevelFiber , $ promise ): void {
299
+ if ($ lowLevelFiber !== null ) {
300
+ FiberMap::unsetPromise ($ lowLevelFiber , $ promise );
301
+ }
302
+
245
303
if (!$ throwable instanceof \Throwable) {
246
304
$ throwable = new \UnexpectedValueException (
247
305
'Promise rejected with unexpected value of type ' . (is_object ($ throwable ) ? get_class ($ throwable ) : gettype ($ throwable ))
@@ -285,6 +343,10 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber): void
285
343
throw $ rejectedThrowable ;
286
344
}
287
345
346
+ if ($ lowLevelFiber !== null ) {
347
+ FiberMap::setPromise ($ lowLevelFiber , $ promise );
348
+ }
349
+
288
350
$ fiber = FiberFactory::create ();
289
351
290
352
return $ fiber ->suspend ();
0 commit comments