Skip to content

Commit e5743bd

Browse files
authored
Merge pull request #25 from clue-labs/pending
Only start timers if input Promise is still pending
2 parents 17c1de0 + 0d96255 commit e5743bd

3 files changed

Lines changed: 63 additions & 8 deletions

File tree

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ It returns a new `Promise` with the following resolution behavior:
5050
* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
5151
* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
5252

53+
Internally, the given `$time` value will be used to start a timer that will
54+
*cancel* the pending operation once it triggers.
55+
This implies that if you pass a really small (or negative) value, it will still
56+
start a timer and will thus trigger at the earliest possible time in the future.
57+
58+
If the input `$promise` is already settled, then the resulting promise will
59+
resolve or reject immediately without starting a timer at all.
60+
5361
A common use case for handling only resolved values looks like this:
5462

5563
```php
@@ -269,6 +277,11 @@ Timer\resolve(1.5, $loop)->then(function ($time) {
269277
});
270278
```
271279

280+
Internally, the given `$time` value will be used to start a timer that will
281+
resolve the promise once it triggers.
282+
This implies that if you pass a really small (or negative) value, it will still
283+
start a timer and will thus trigger at the earliest possible time in the future.
284+
272285
#### Resolve cancellation
273286

274287
You can explicitly `cancel()` the resulting timer promise at any time:
@@ -292,6 +305,11 @@ Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
292305
});
293306
```
294307

308+
Internally, the given `$time` value will be used to start a timer that will
309+
reject the promise once it triggers.
310+
This implies that if you pass a really small (or negative) value, it will still
311+
start a timer and will thus trigger at the earliest possible time in the future.
312+
295313
This function complements the [`resolve()`](#resolve) function
296314
and can be used as a basic building block for higher-level promise consumers.
297315

src/functions.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,34 @@ function timeout(PromiseInterface $promise, $time, LoopInterface $loop)
1717
}
1818

1919
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) {
20+
$timer = null;
21+
$promise->then(function ($v) use (&$timer, $loop, $resolve) {
22+
if ($timer) {
23+
$loop->cancelTimer($timer);
24+
}
25+
$timer = false;
26+
$resolve($v);
27+
}, function ($v) use (&$timer, $loop, $reject) {
28+
if ($timer) {
29+
$loop->cancelTimer($timer);
30+
}
31+
$timer = false;
32+
$reject($v);
33+
});
34+
35+
// promise already resolved => no need to start timer
36+
if ($timer === false) {
37+
return;
38+
}
39+
40+
// start timeout timer which will cancel the input promise
2041
$timer = $loop->addTimer($time, function () use ($time, $promise, $reject) {
2142
$reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds'));
2243

2344
if ($promise instanceof CancellablePromiseInterface) {
2445
$promise->cancel();
2546
}
2647
});
27-
28-
$promise->then(function ($v) use ($timer, $loop, $resolve) {
29-
$loop->cancelTimer($timer);
30-
$resolve($v);
31-
}, function ($v) use ($timer, $loop, $reject) {
32-
$loop->cancelTimer($timer);
33-
$reject($v);
34-
});
3548
}, $canceller);
3649
}
3750

tests/FunctionTimeoutTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,30 @@ public function testCancelTimeoutWithoutCancellationhandlerWillNotCancelTimerAnd
9898
$this->expectPromisePending($timeout);
9999
}
100100

101+
public function testResolvedPromiseWillNotStartTimer()
102+
{
103+
$promise = new \React\Promise\Promise(function ($resolve) { $resolve(true); });
104+
105+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
106+
$loop->expects($this->never())->method('addTimer');
107+
108+
$timeout = Timer\timeout($promise, 0.01, $loop);
109+
110+
$this->expectPromiseResolved($timeout);
111+
}
112+
113+
public function testRejectedPromiseWillNotStartTimer()
114+
{
115+
$promise = Promise\reject(new \RuntimeException());
116+
117+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
118+
$loop->expects($this->never())->method('addTimer');
119+
120+
$timeout = Timer\timeout($promise, 0.01, $loop);
121+
122+
$this->expectPromiseRejected($timeout);
123+
}
124+
101125
public function testCancelTimeoutWillCancelGivenPromise()
102126
{
103127
$promise = new \React\Promise\Promise(function () { }, $this->expectCallableOnce());

0 commit comments

Comments
 (0)