diff --git a/lib/Promise.php b/lib/Promise.php index 7fff9a9..913dd21 100644 --- a/lib/Promise.php +++ b/lib/Promise.php @@ -11,15 +11,18 @@ namespace Amp; interface Promise { /** - * Registers a callback to be invoked when the promise is resolved. + * Registers a callback to be invoked when the promise is resolved. Note that using this method directly is + * generally not recommended. Use the {@see await()} function to await promise resolution or use one of the + * combinator functions in the Amp\Promise namespace, such as {@see \Amp\Promise\all()}. * * If this method is called multiple times, additional handlers will be registered instead of replacing any already * existing handlers. * - * If the promise is already resolved, the callback MUST be executed immediately. + * Registered callbacks MUST be invoked asynchronously when the promise is resolved using a defer watcher in the + * event-loop. * * Exceptions MUST NOT be thrown from this method. Any exceptions thrown from invoked callbacks MUST be - * forwarded to the event-loop error handler. + * forwarded to the event-loop error handler by re-throwing from a defer watcher. * * Note: You shouldn't implement this interface yourself. Instead, provide a method that returns a promise for the * operation you're implementing. Objects other than pure placeholders implementing it are a very bad idea. @@ -27,9 +30,7 @@ interface Promise * @param callable $onResolved The first argument shall be `null` on success, while the second shall be `null` on * failure. * - * @psalm-param callable(\Throwable|null, mixed): (Promise|\React\Promise\PromiseInterface|\Generator, mixed, - * mixed>|null) | callable(\Throwable|null, mixed): void $onResolved + * @psalm-param callable(\Throwable|null, mixed):Promise|null $onResolved * * @return void */ diff --git a/lib/functions.php b/lib/functions.php index 1bf46c4..09e0dcc 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -30,13 +30,14 @@ namespace Amp $fiber = \Fiber::this(); $resolved = false; - if ($fiber) { - if (isset($loop) && $fiber === $loop) { + if ($fiber) { // Awaiting from within a fiber. + if ($fiber === $loop) { throw new \Error(\sprintf('Cannot call %s() within an event loop callback', __FUNCTION__)); } $promise->onResolve(static function (?\Throwable $exception, mixed $value) use (&$resolved, $fiber): void { $resolved = true; + if ($exception) { $fiber->throw($exception); return; @@ -49,14 +50,16 @@ namespace Amp $value = \Fiber::suspend(); if (!$resolved) { - // $resolved should only be false if the function set in Promise::onResolve() did not resume the fiber. + // $resolved should only be false if the fiber was manually resumed outside of the callback above. throw new \Error('Fiber resumed before promise was resolved'); } return $value; } - if (!isset($loop) || $loop->isTerminated()) { + // Awaiting from {main}. + + if (!$loop || $loop->isTerminated()) { $loop = new \Fiber(static fn() => Loop::getDriver()->run()); // Run event loop to completion on shutdown. \register_shutdown_function(static function () use ($loop): void { @@ -68,26 +71,28 @@ namespace Amp $promise->onResolve(static function (?\Throwable $exception, mixed $value) use (&$resolved): void { $resolved = true; + // Suspend event loop fiber to {main}. - \Fiber::suspend([$exception, $value]); + if ($exception) { + \Fiber::suspend(static fn() => throw $exception); + return; + } + + \Fiber::suspend(static fn() => $value); }); try { - [$exception, $value] = $loop->isStarted() ? $loop->resume() : $loop->start(); + $lambda = $loop->isStarted() ? $loop->resume() : $loop->start(); } catch (\Throwable $exception) { throw new \Error('Exception unexpectedly thrown from event loop', 0, $exception); } if (!$resolved) { - // $resolved should only be false if the function set in Promise::onResolve() did not resume the fiber. + // $resolved should only be false if the event loop exited without resolving the promise. throw new \Error('Event loop suspended or exited without resolving the promise'); } - if ($exception) { - throw $exception; - } - - return $value; + return $lambda(); } /**