diff --git a/src/Promise.php b/src/Promise.php index 09672c11..7cd49acd 100644 --- a/src/Promise.php +++ b/src/Promise.php @@ -9,7 +9,7 @@ final class Promise implements PromiseInterface private $handlers = []; - private $remainingCancelRequests = 0; + private $requiredCancelRequests = 0; public function __construct(callable $resolver, callable $canceller = null) { @@ -27,14 +27,14 @@ public function then(callable $onFulfilled = null, callable $onRejected = null) return new static($this->resolver($onFulfilled, $onRejected)); } - $this->remainingCancelRequests++; + $this->requiredCancelRequests++; return new static($this->resolver($onFulfilled, $onRejected), function () { - if (--$this->remainingCancelRequests > 0) { - return; - } + $this->requiredCancelRequests--; - $this->cancel(); + if ($this->requiredCancelRequests <= 0) { + $this->cancel(); + } }); } @@ -76,14 +76,37 @@ public function always(callable $onFulfilledOrRejected) public function cancel() { - if (null === $this->canceller) { - return; - } - $canceller = $this->canceller; $this->canceller = null; - $this->call($canceller); + $parentCanceller = null; + + if (null !== $this->result) { + // Go up the promise chain and reach the top most promise which is + // itself not following another promise + $root = $this->unwrap($this->result); + + // Return if the root promise is already resolved or a + // FulfilledPromise or RejectedPromise + if (!$root instanceof self || null !== $root->result) { + return; + } + + $root->requiredCancelRequests--; + + if ($root->requiredCancelRequests <= 0) { + $parentCanceller = [$root, 'cancel']; + } + } + + if (null !== $canceller) { + $this->call($canceller); + } + + // For BC, we call the parent canceller after our own canceller + if ($parentCanceller) { + $parentCanceller(); + } } private function resolver(callable $onFulfilled = null, callable $onRejected = null) @@ -119,10 +142,22 @@ private function settle(PromiseInterface $result) { $result = $this->unwrap($result); + if ($result === $this) { + $result = new RejectedPromise( + new \LogicException('Cannot resolve a promise with itself.') + ); + } + + if ($result instanceof self) { + $result->requiredCancelRequests++; + } else { + // Unset canceller only when not following a pending promise + $this->canceller = null; + } + $handlers = $this->handlers; $this->handlers = []; - $this->canceller = null; $this->result = $result; foreach ($handlers as $handler) { @@ -136,12 +171,6 @@ private function unwrap($promise) $promise = $promise->result; } - if ($promise === $this) { - return new RejectedPromise( - new \LogicException('Cannot resolve a promise with itself.') - ); - } - return $promise; }