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
+ foreach (FiberMap::getPromises ($ fiber ) as $ promise ) {
202
+ if ($ promise instanceof CancellablePromiseInterface) {
203
+ $ promise ->cancel ();
204
+ }
164
205
}
165
206
});
166
207
167
- $ fiber ->start ();
168
- });
208
+ $ lowLevelFiber = \Fiber::getCurrent ();
209
+ if ($ lowLevelFiber !== null ) {
210
+ FiberMap::attachPromise ($ lowLevelFiber , $ promise );
211
+ }
212
+
213
+ return $ promise ;
214
+ };
169
215
}
170
216
171
217
@@ -230,9 +276,18 @@ function await(PromiseInterface $promise): mixed
230
276
$ rejected = false ;
231
277
$ resolvedValue = null ;
232
278
$ rejectedThrowable = null ;
279
+ $ lowLevelFiber = \Fiber::getCurrent ();
280
+
281
+ if ($ lowLevelFiber !== null && FiberMap::isCancelled ($ lowLevelFiber ) && $ promise instanceof CancellablePromiseInterface) {
282
+ $ promise ->cancel ();
283
+ }
233
284
234
285
$ promise ->then (
235
- function (mixed $ value ) use (&$ resolved , &$ resolvedValue , &$ fiber ): void {
286
+ function (mixed $ value ) use (&$ resolved , &$ resolvedValue , &$ fiber , $ lowLevelFiber , $ promise ): void {
287
+ if ($ lowLevelFiber !== null ) {
288
+ FiberMap::detachPromise ($ lowLevelFiber , $ promise );
289
+ }
290
+
236
291
if ($ fiber === null ) {
237
292
$ resolved = true ;
238
293
$ resolvedValue = $ value ;
@@ -241,7 +296,11 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber): void {
241
296
242
297
$ fiber ->resume ($ value );
243
298
},
244
- function (mixed $ throwable ) use (&$ rejected , &$ rejectedThrowable , &$
1241
span>fiber ): void {
299
+ function (mixed $ throwable ) use (&$ rejected , &$ rejectedThrowable , &$ fiber , $ lowLevelFiber , $ promise ): void {
300
+ if ($ lowLevelFiber !== null ) {
301
+ FiberMap::detachPromise ($ lowLevelFiber , $ promise );
302
+ }
303
+
245
304
if (!$ throwable instanceof \Throwable) {
246
305
$ throwable = new \UnexpectedValueException (
247
306
'Promise rejected with unexpected value of type ' . (is_object ($ throwable ) ? get_class ($ throwable ) : gettype ($ throwable ))
@@ -285,6 +344,10 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber): void
285
344
throw $ rejectedThrowable ;
286
345
}
287
346
347
+ if ($ lowLevelFiber !== null ) {
348
+ FiberMap::attachPromise ($ lowLevelFiber , $ promise );
349
+ }
350
+
288
351
$ fiber = FiberFactory::create ();
289
352
290
353
return $ fiber ->suspend ();
0 commit comments