Skip to content

Commit f1cfed0

Browse files
committed
Add cancellation support for resolve() and reject()
1 parent a3b8c28 commit f1cfed0

4 files changed

Lines changed: 96 additions & 3 deletions

File tree

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,20 +176,54 @@ Timer\resolve(1.5, $loop)->then(function ($time) {
176176
});
177177
```
178178

179+
#### Resolve cancellation
180+
181+
You can explicitly `cancel()` the resulting timer promise at any time:
182+
183+
```php
184+
$timer = Timer\resolve(2.0, $loop);
185+
186+
$timer->cancel();
187+
```
188+
189+
This will abort the timer and *reject* with a `RuntimeException`.
190+
191+
> Note: If you're stuck on legacy versions (PHP 5.3), then the `cancel()` method
192+
is not available, as the Promise cancellation API is currently only available in
193+
[react/promise v2.1.0](https://github.com/reactphp/promise)
194+
which in turn requires PHP 5.4 or up.
195+
179196
### reject()
180197

181198
The `reject($time, LoopInterface $loop)` function can be used to create a new Promise
182199
which rejects in `$time` seconds with a `TimeoutException`.
183200

184201
```php
185202
Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
186-
echo '
203+
echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
187204
});
188205
```
189206

190207
This function complements the [`resolve()`](#resolve) function
191208
and can be used as a basic building block for higher-level promise consumers.
192209

210+
#### Reject cancellation
211+
212+
You can explicitly `cancel()` the resulting timer promise at any time:
213+
214+
```php
215+
$timer = Timer\reject(2.0, $loop);
216+
217+
$timer->cancel();
218+
```
219+
220+
This will abort the timer and *reject* with a `RuntimeException`.
221+
222+
> Note: If you're stuck on legacy versions (PHP 5.3), then the `cancel()` method
223+
is not available, as the Promise cancellation API is currently only available in
224+
[react/promise v2.1.0](https://github.com/reactphp/promise)
225+
which in turn requires PHP 5.4 or up.
226+
193227
### TimeoutException
194228

195229
The `TimeoutException` extends PHP's built-in `RuntimeException`.

src/functions.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,15 @@ function timeout(PromiseInterface $promise, $time, LoopInterface $loop)
3030

3131
function resolve($time, LoopInterface $loop)
3232
{
33-
return new Promise(function ($resolve) use ($loop, $time) {
34-
$loop->addTimer($time, function () use ($time, $resolve) {
33+
return new Promise(function ($resolve) use ($loop, $time, &$timer) {
34+
// resolve the promise when the timer fires in $time seconds
35+
$timer = $loop->addTimer($time, function () use ($time, $resolve) {
3536
$resolve($time);
3637
});
38+
}, function ($resolveUnused, $reject) use (&$timer, $loop) {
39+
// cancelling this promise will cancel the timer and reject
40+
$loop->cancelTimer($timer);
41+
$reject(new \RuntimeException('Timer cancelled'));
3742
});
3843
}
3944

tests/FunctionRejectTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use React\Promise\Timer;
4+
use React\Promise\CancellablePromiseInterface;
45

56
class FunctionRejectTest extends TestCase
67
{
@@ -19,4 +20,17 @@ public function testPromiseWillBeRejectedOnTimeout()
1920

2021
$this->expectPromiseRejected($promise);
2122
}
23+
24+
public function testCancelingPromiseWillRejectTimer()
25+
{
26+
$promise = Timer\reject(0.01, $this->loop);
27+
28+
if (!($promise instanceof CancellablePromiseInterface)) {
29+
$this->markTestSkipped('Outdated Promise API');
30+
}
31+
32+
$promise->cancel();
33+
34+
$this->expectPromiseRejected($promise);
35+
}
2236
}

tests/FunctionResolveTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use React\Promise\Timer;
4+
use React\Promise\CancellablePromiseInterface;
45

56
class FunctionResolveTest extends TestCase
67
{
@@ -19,4 +20,43 @@ public function testPromiseWillBeResolvedOnTimeout()
1920

2021
$this->expectPromiseResolved($promise);
2122
}
23+
24+
public function testWillStartLoopTimer()
25+
{
26+
$loop = $this->getMock('React\EventLoop\LoopInterface');
27+
$loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01));
28+
29+
Timer\resolve(0.01, $loop);
30+
}
31+
32+
public function testCancellingPromiseWillCancelLoopTimer()
33+
{
34+
$loop = $this->getMock('React\EventLoop\LoopInterface');
35+
36+
$timer = $this->getMock('React\EventLoop\Timer\TimerInterface');
37+
$loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
38+
39+
$promise = Timer\resolve(0.01, $loop);
40+
41+
if (!($promise instanceof CancellablePromiseInterface)) {
42+
$this->markTestSkipped('Outdated Promise API');
43+
}
44+
45+
$loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer));
46+
47+
$promise->cancel();
48+
}
49+
50+
public function testCancelingPromiseWillRejectTimer()
51+
{
52+
$promise = Timer\resolve(0.01, $this->loop);
53+
54+
if (!($promise instanceof CancellablePromiseInterface)) {
55+
$this->markTestSkipped('Outdated Promise API');
56+
}
57+
58+
$promise->cancel();
59+
60+
$this->expectPromiseRejected($promise);
61+
}
2262
}

0 commit comments

Comments
 (0)