Skip to content

Commit

Permalink
Ensure promise is only cancelled if all derived promises cancel
Browse files Browse the repository at this point in the history
  • Loading branch information
jsor committed Sep 17, 2014
1 parent 75fbae0 commit 8b7e0f0
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ If the resolver or canceller throw an exception, the promise will be rejected
with that thrown exception as the rejection reason.

The resolver function will be called immediately, the canceller function only
once a consumer calls the `cancel()` method of the promise.
once all consumers called the `cancel()` method of the promise.

### FulfilledPromise

Expand Down
9 changes: 9 additions & 0 deletions src/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class Promise implements PromiseInterface, CancellablePromiseInterface
private $handlers = [];
private $progressHandlers = [];

private $requiredCancelRequests = 0;
private $cancelRequests = 0;

public function __construct(callable $resolver, callable $canceller = null)
{
$this->canceller = $canceller;
Expand All @@ -22,7 +25,13 @@ public function then(callable $onFulfilled = null, callable $onRejected = null,
return $this->result->then($onFulfilled, $onRejected, $onProgress);
}

$this->requiredCancelRequests++;

return new static($this->resolver($onFulfilled, $onRejected, $onProgress), function($resolve, $reject, $progress) {
if (++$this->cancelRequests < $this->requiredCancelRequests) {
return;
}

$this->cancel();
});
}
Expand Down
46 changes: 46 additions & 0 deletions tests/PromiseTest/CancelTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,50 @@ public function cancelShouldCallCancellerFromDeepNestedPromiseChain()

$promise->cancel();
}

/** @test */
public function cancelCalledOnChildrenSouldOnlyCancelWhenAllChildrenCancelled()
{
$adapter = $this->getPromiseTestAdapter($this->expectCallableNever());

$child1 = $adapter->promise()
->then()
->then();

$adapter->promise()
->then();

$child1->cancel();
}

/** @test */
public function cancelShouldTriggerCancellerWhenAllChildrenCancel()
{
$adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());

$child1 = $adapter->promise()
->then()
->then();

$child2 = $adapter->promise()
->then();

$child1->cancel();
$child2->cancel();
}

/** @test */
public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise()
{
$adapter = $this->getPromiseTestAdapter($this->expectCallableOnce());

$adapter->promise()
->then()
->then();

$adapter->promise()
->then();

$adapter->promise()->cancel();
}
}

0 comments on commit 8b7e0f0

Please sign in to comment.