|
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