148148 * }); 
149149 * ``` 
150150 * 
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+  * 
151176 * @param callable(mixed ...$args):mixed $function 
152177 * @return callable(): PromiseInterface<mixed> 
153178 * @since 4.0.0 
154179 * @see coroutine() 
155180 */ 
156181function  async (callable  $ functioncallable 
157182{
158-     return  static  fn  (mixed  ...$ argsPromiseInterface new  Promise (function  (callable  $ resolvecallable  $ rejectuse  ($ function$ argsvoid  {
159-         $ fibernew  \Fiber (function  () use  ($ resolve$ reject$ function$ argsvoid  {
160-             try  {
161-                 $ resolve$ function$ args
162-             } catch  (\Throwable   $ exception
163-                 $ reject$ exception
183+     return  static  function  (mixed  ...$ argsuse  ($ functionPromiseInterface 
184+         $ fibernull ;
185+         $ promisenew  Promise (function  (callable  $ resolvecallable  $ rejectuse  ($ function$ args$ fibervoid  {
186+             $ fibernew  \Fiber (function  () use  ($ resolve$ reject$ function$ args$ fibervoid  {
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+             $ fiberstart ();
199+         }, function  () use  (&$ fibervoid  {
200+             FiberMap::cancel ($ fiber
201+             foreach  (FiberMap::getPromises ($ fiberas  $ promise
202+                 if  ($ promiseinstanceof  CancellablePromiseInterface) {
203+                     $ promisecancel ();
204+                 }
164205            }
165206        });
166207
167-         $ fiberstart ();
168-     });
208+         $ lowLevelFibergetCurrent ();
209+         if  ($ lowLevelFibernull ) {
210+             FiberMap::attachPromise ($ lowLevelFiber$ promise
211+         }
212+ 
213+         return  $ promise
214+     };
169215}
170216
171217
@@ -230,9 +276,18 @@ function await(PromiseInterface $promise): mixed
230276    $ rejectedfalse ;
231277    $ resolvedValuenull ;
232278    $ rejectedThrowablenull ;
279+     $ lowLevelFibergetCurrent ();
280+ 
281+     if  ($ lowLevelFibernull  && FiberMap::isCancelled ($ lowLevelFiber$ promiseinstanceof  CancellablePromiseInterface) {
282+         $ promisecancel ();
283+     }
233284
234285    $ promisethen (
235-         function  (mixed  $ valueuse  (&$ resolved$ resolvedValue$ fibervoid  {
286+         function  (mixed  $ valueuse  (&$ resolved$ resolvedValue$ fiber$ lowLevelFiber$ promisevoid  {
287+             if  ($ lowLevelFibernull ) {
288+                 FiberMap::detachPromise ($ lowLevelFiber$ promise
289+             }
290+ 
236291            if  ($ fibernull ) {
237292                $ resolvedtrue ;
238293                $ resolvedValue$ value
@@ -241,7 +296,11 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber): void {
241296
242297            $ fiberresume ($ value
243298        },
244-         function  (mixed  $ throwableuse  (&$ rejected$ rejectedThrowable$ fibervoid  {
299+         function  (mixed  $ throwableuse  (&$ rejected$ rejectedThrowable$ fiber$ lowLevelFiber$ promisevoid  {
300+             if  ($ lowLevelFibernull ) {
301+                 FiberMap::detachPromise ($ lowLevelFiber$ promise
302+             }
303+ 
245304            if  (!$ throwableinstanceof  \Throwable) {
246305                $ throwablenew  \UnexpectedValueException (
247306                    'Promise rejected with unexpected value of type  '  . (is_object ($ throwableget_class ($ throwablegettype ($ throwable
@@ -285,6 +344,10 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber): void
285344        throw  $ rejectedThrowable
286345    }
287346
347+     if  ($ lowLevelFibernull ) {
348+         FiberMap::attachPromise ($ lowLevelFiber$ promise
349+     }
350+ 
288351    $ fibercreate ();
289352
290353    return  $ fibersuspend ();
0 commit comments