|
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; |
@@ -285,6 +340,10 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber): void |
285 | 340 | throw $rejectedThrowable; |
286 | 341 | } |
287 | 342 |
|
| 343 | + if ($lowLevelFiber !== null) { |
| 344 | + FiberMap::attachPromise($lowLevelFiber, $promise); |
| 345 | + } |
| 346 | + |
288 | 347 | $fiber = FiberFactory::create(); |
289 | 348 |
|
290 | 349 | return $fiber->suspend(); |
|
0 commit comments