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+             $ promisegetPromise ($ fiber
202+             if  ($ promiseinstanceof  CancellablePromiseInterface) {
203+                 $ promisecancel ();
164204            }
165205        });
166206
167-         $ fiberstart ();
168-     });
207+         $ lowLevelFibergetCurrent ();
208+         if  ($ lowLevelFibernull ) {
209+             FiberMap::setPromise ($ lowLevelFiber$ promise
210+         }
211+ 
212+         return  $ promise
213+     };
169214}
170215
171216
@@ -230,9 +275,18 @@ function await(PromiseInterface $promise): mixed
230275    $ rejectedfalse ;
231276    $ resolvedValuenull ;
232277    $ rejectedThrowablenull ;
278+     $ lowLevelFibergetCurrent ();
279+ 
280+     if  ($ lowLevelFibernull  && FiberMap::isCancelled ($ lowLevelFiber$ promiseinstanceof  CancellablePromiseInterface) {
281+         $ promisecancel ();
282+     }
233283
234284    $ promisethen (
235-         function  (mixed  $ valueuse  (&$ resolved$ resolvedValue$ fibervoid  {
285+         function  (mixed  $ valueuse  (&$ resolved$ resolvedValue$ fiber$ lowLevelFiber$ promisevoid  {
286+             if  ($ lowLevelFibernull ) {
287+                 FiberMap::unsetPromise ($ lowLevelFiber$ promise
288+             }
289+ 
236290            if  ($ fibernull ) {
237291                $ resolvedtrue ;
238292                $ resolvedValue$ value
@@ -241,7 +295,11 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber): void {
241295
242296            $ fiberresume ($ value
243297        },
244-         function  (mixed  $ throwableuse  (&$ rejected$ rejectedThrowable$ fibervoid  {
298+         function  (mixed  $ throwableuse  (&$ rejected$ rejectedThrowable$ fiber$ lowLevelFiber$ promisevoid  {
299+             if  ($ lowLevelFibernull ) {
300+                 FiberMap::unsetPromise ($ lowLevelFiber$ promise
301+             }
302+ 
245303            if  (!$ throwableinstanceof  \Throwable) {
246304                $ throwablenew  \UnexpectedValueException (
247305                    'Promise rejected with unexpected value of type  '  . (is_object ($ throwableget_class ($ throwablegettype ($ throwable
@@ -285,6 +343,10 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber): void
285343        throw  $ rejectedThrowable
286344    }
287345
346+     if  ($ lowLevelFibernull ) {
347+         FiberMap::setPromise ($ lowLevelFiber$ promise
348+     }
349+ 
288350    $ fibercreate ();
289351
290352    return  $ fibersuspend ();
0 commit comments