Skip to content

Commit

Permalink
Implement follower cancellation propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
jsor committed Sep 19, 2017
1 parent 5d58c47 commit 9fd4bd8
Showing 1 changed file with 47 additions and 18 deletions.
65 changes: 47 additions & 18 deletions src/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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();
}
});
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand Down

0 comments on commit 9fd4bd8

Please sign in to comment.