-
-
Notifications
You must be signed in to change notification settings - Fork 125
Description
PHP version: 8.2.12 (hint: php --version)
Description
If an EachPromise (or methods reliant of it like Each::of) is constructed with an empty iterable, it returns a promise in the pending state that only completes when waited on. In contrast, if the iterator has at least one item (even a fulfilled promise!) then it can be completed by running the queue.
It would be nice if things were more consistent. Either Each should always use the queue or it should complete synchronously if there is no async work to be done.
How to reproduce
// Empty case
$p = Each::of((function($b) { if ($b) { yield 1; } })(false));
$p->getState(); // "pending"
$p->then(function() { echo "DONE!"; });
\GuzzleHttp\Promise\Utils::queue()->run(); // does not print "DONE!"
// Non-empty case
$p = Each::of((function($b) { if ($b) { yield 1; } })(true));
$p->then(function() { echo "DONE!"; });
\GuzzleHttp\Promise\Utils::queue()->run(); // prints "DONE!"
Possible Solution
One solution would be to add the following code at the start of EachPromise::createPromise():
if (!$this->iterable->valid()) { return $this->aggregate = new FulfilledPromise(null); }
This way, it would complete synchronously with an empty iterator.
Additional context
The reason this is impacting me is that I'm using php fibers as a layer on top of Guzzle. When I want to wait on a promise, I need to pump the event loop (via CurlMultiHandler::tick()) to advance all promises until the current one is complete. This doesn't work if a promise becomes "orphaned" such that it is neither complete nor completable via the queue or the curl handler.