Skip to content

Commit 997c10e

Browse files
authored
Merge pull request #51 from clue-labs/sleep
Add new `sleep()` function and deprecate `resolve()` and `reject()` functions
2 parents 9ccdc9b + b9469d3 commit 997c10e

File tree

3 files changed

+198
-14
lines changed

3 files changed

+198
-14
lines changed

README.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](
88

99
* [Usage](#usage)
1010
* [timeout()](#timeout)
11-
* [resolve()](#resolve)
12-
* [reject()](#reject)
11+
* [sleep()](#sleep)
12+
* [~~resolve()~~](#resolve)
13+
* [~~reject()~~](#reject)
1314
* [TimeoutException](#timeoutexception)
1415
* [getTimeout()](#gettimeout)
1516
* [Install](#install)
@@ -171,7 +172,41 @@ The applies to all promise collection primitives alike, i.e. `all()`,
171172
For more details on the promise primitives, please refer to the
172173
[Promise documentation](https://github.com/reactphp/promise#functions).
173174

174-
### resolve()
175+
### sleep()
176+
177+
The `sleep(float $time, ?LoopInterface $loop = null): PromiseInterface<void, RuntimeException>` function can be used to
178+
create a new promise that resolves in `$time` seconds.
179+
180+
```php
181+
React\Promise\Timer\sleep(1.5)->then(function () {
182+
echo 'Thanks for waiting!' . PHP_EOL;
183+
});
184+
```
185+
186+
Internally, the given `$time` value will be used to start a timer that will
187+
resolve the promise once it triggers. This implies that if you pass a really
188+
small (or negative) value, it will still start a timer and will thus trigger
189+
at the earliest possible time in the future.
190+
191+
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
192+
pass the event loop instance to use. You can use a `null` value here in order to
193+
use the [default loop](https://github.com/reactphp/event-loop#loop). This value
194+
SHOULD NOT be given unless you're sure you want to explicitly use a given event
195+
loop instance.
196+
197+
The returned promise is implemented in such a way that it can be cancelled
198+
when it is still pending. Cancelling a pending promise will reject its value
199+
with a `RuntimeException` and clean up any pending timers.
200+
201+
```php
202+
$timer = React\Promise\Timer\sleep(2.0);
203+
204+
$timer->cancel();
205+
```
206+
207+
### ~~resolve()~~
208+
209+
> Deprecated since v1.8.0, see [`sleep()`](#sleep) instead.
175210
176211
The `resolve(float $time, ?LoopInterface $loop = null): PromiseInterface<float, RuntimeException>` function can be used to
177212
create a new promise that resolves in `$time` seconds with the `$time` as the fulfillment value.
@@ -203,7 +238,9 @@ $timer = React\Promise\Timer\resolve(2.0);
203238
$timer->cancel();
204239
```
205240

206-
### reject()
241+
### ~~reject()~~
242+
243+
> Deprecated since v1.8.0, see [`sleep()`](#sleep) instead.
207244
208245
The `reject(float $time, ?LoopInterface $loop = null): PromiseInterface<void, TimeoutException|RuntimeException>` function can be used to
209246
create a new promise which rejects in `$time` seconds with a `TimeoutException`.

src/functions.php

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,11 @@ function timeout(PromiseInterface $promise, $time, LoopInterface $loop = null)
193193
}
194194

195195
/**
196-
* Create a new promise that resolves in `$time` seconds with the `$time` as the fulfillment value.
196+
* Create a new promise that resolves in `$time` seconds.
197197
*
198198
* ```php
199-
* React\Promise\Timer\resolve(1.5)->then(function ($time) {
200-
* echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
199+
* React\Promise\Timer\sleep(1.5)->then(function () {
200+
* echo 'Thanks for waiting!' . PHP_EOL;
201201
* });
202202
* ```
203203
*
@@ -217,16 +217,16 @@ function timeout(PromiseInterface $promise, $time, LoopInterface $loop = null)
217217
* with a `RuntimeException` and clean up any pending timers.
218218
*
219219
* ```php
220-
* $timer = React\Promise\Timer\resolve(2.0);
220+
* $timer = React\Promise\Timer\sleep(2.0);
221221
*
222222
* $timer->cancel();
223223
* ```
224224
*
225225
* @param float $time
226226
* @param ?LoopInterface $loop
227-
* @return PromiseInterface<float, \RuntimeException>
227+
* @return PromiseInterface<void, \RuntimeException>
228228
*/
229-
function resolve($time, LoopInterface $loop = null)
229+
function sleep($time, LoopInterface $loop = null)
230230
{
231231
if ($loop === null) {
232232
$loop = Loop::get();
@@ -235,8 +235,8 @@ function resolve($time, LoopInterface $loop = null)
235235
$timer = null;
236236
return new Promise(function ($resolve) use ($loop, $time, &$timer) {
237237
// resolve the promise when the timer fires in $time seconds
238-
$timer = $loop->addTimer($time, function () use ($time, $resolve) {
239-
$resolve($time);
238+
$timer = $loop->addTimer($time, function () use ($resolve) {
239+
$resolve();
240240
});
241241
}, function () use (&$timer, $loop) {
242242
// cancelling this promise will cancel the timer, clean the reference
@@ -249,7 +249,50 @@ function resolve($time, LoopInterface $loop = null)
249249
}
250250

251251
/**
252-
* Create a new promise which rejects in `$time` seconds with a `TimeoutException`.
252+
* [Deprecated] Create a new promise that resolves in `$time` seconds with the `$time` as the fulfillment value.
253+
*
254+
* ```php
255+
* React\Promise\Timer\resolve(1.5)->then(function ($time) {
256+
* echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
257+
* });
258+
* ```
259+
*
260+
* Internally, the given `$time` value will be used to start a timer that will
261+
* resolve the promise once it triggers. This implies that if you pass a really
262+
* small (or negative) value, it will still start a timer and will thus trigger
263+
* at the earliest possible time in the future.
264+
*
265+
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
266+
* pass the event loop instance to use. You can use a `null` value here in order to
267+
* use the [default loop](https://github.com/reactphp/event-loop#loop). This value
268+
* SHOULD NOT be given unless you're sure you want to explicitly use a given event
269+
* loop instance.
270+
*
271+
* The returned promise is implemented in such a way that it can be cancelled
272+
* when it is still pending. Cancelling a pending promise will reject its value
273+
* with a `RuntimeException` and clean up any pending timers.
274+
*
275+
* ```php
276+
* $timer = React\Promise\Timer\resolve(2.0);
277+
*
278+
* $timer->cancel();
279+
* ```
280+
*
281+
* @param float $time
282+
* @param ?LoopInterface $loop
283+
* @return PromiseInterface<float, \RuntimeException>
284+
* @deprecated 1.8.0 See `sleep()` instead
285+
* @see sleep()
286+
*/
287+
function resolve($time, LoopInterface $loop = null)
288+
{
289+
return sleep($time, $loop)->then(function() use ($time) {
290+
return $time;
291+
});
292+
}
293+
294+
/**
295+
* [Deprecated] Create a new promise which rejects in `$time` seconds with a `TimeoutException`.
253296
*
254297
* ```php
255298
* React\Promise\Timer\reject(2.0)->then(null, function (React\Promise\Timer\TimeoutException $e) {
@@ -281,10 +324,12 @@ function resolve($time, LoopInterface $loop = null)
281324
* @param float $time
282325
* @param LoopInterface $loop
283326
* @return PromiseInterface<void, TimeoutException|\RuntimeException>
327+
* @deprecated 1.8.0 See `sleep()` instead
328+
* @see sleep()
284329
*/
285330
function reject($time, LoopInterface $loop = null)
286331
{
287-
return resolve($time, $loop)->then(function ($time) {
332+
return sleep($time, $loop)->then(function () use ($time) {
288333
throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
289334
});
290335
}

tests/FunctionSleepTest.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
namespace React\Tests\Promise\Timer;
4+
5+
use React\EventLoop\Loop;
6+
use React\Promise\Timer;
7+
8+
class FunctionSleepTest extends TestCase
9+
{
10+
public function testPromiseIsPendingWithoutRunningLoop()
11+
{
12+
$promise = Timer\sleep(0.01);
13+
14+
$this->expectPromisePending($promise);
15+
}
16+
17+
public function testPromiseExpiredIsPendingWithoutRunningLoop()
18+
{
19+
$promise = Timer\sleep(-1);
20+
21+
$this->expectPromisePending($promise);
22+
}
23+
24+
public function testPromiseWillBeResolvedOnTimeout()
25+
{
26+
$promise = Timer\sleep(0.01);
27+
28+
Loop::run();
29+
30+
$this->expectPromiseResolved($promise);
31+
}
32+
33+
public function testPromiseExpiredWillBeResolvedOnTimeout()
34+
{
35+
$promise = Timer\sleep(-1);
36+
37+
Loop::run();
38+
39+
$this->expectPromiseResolved($promise);
40+
}
41+
42+
public function testWillStartLoopTimer()
43+
{
44+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
45+
$loop->expects($this->once())->method('addTimer')->with($this->equalTo(0.01));
46+
47+
Timer\sleep(0.01, $loop);
48+
}
49+
50+
public function testCancellingPromiseWillCancelLoopTimer()
51+
{
52+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
53+
54+
$timer = $this->getMockBuilder(interface_exists('React\EventLoop\TimerInterface') ? 'React\EventLoop\TimerInterface' : 'React\EventLoop\Timer\TimerInterface')->getMock();
55+
$loop->expects($this->once())->method('addTimer')->will($this->returnValue($timer));
56+
57+
$promise = Timer\sleep(0.01, $loop);
58+
59+
$loop->expects($this->once())->method('cancelTimer')->with($this->equalTo($timer));
60+
61+
$promise->cancel();
62+
}
63+
64+
public function testCancellingPromiseWillRejectTimer()
65+
{
66+
$promise = Timer\sleep(0.01);
67+
68+
$promise->cancel();
69+
70+
$this->expectPromiseRejected($promise);
71+
}
72+
73+
public function testWaitingForPromiseToResolveDoesNotLeaveGarbageCycles()
74+
{
75+
if (class_exists('React\Promise\When')) {
76+
$this->markTestSkipped('Not supported on legacy Promise v1 API');
77+
}
78+
79+
gc_collect_cycles();
80+
81+
$promise = Timer\sleep(0.01);
82+
Loop::run();
83+
unset($promise);
84+
85+
$this->assertEquals(0, gc_collect_cycles());
86+
}
87+
88+
public function testCancellingPromiseDoesNotLeaveGarbageCycles()
89+
{
90+
if (class_exists('React\Promise\When')) {
91+
$this->markTestSkipped('Not supported on legacy Promise v1 API');
92+
}
93+
94+
gc_collect_cycles();
95+
96+
$promise = Timer\sleep(0.01);
97+
$promise->cancel();
98+
unset($promise);
99+
100+
$this->assertEquals(0, gc_collect_cycles());
101+
}
102+
}

0 commit comments

Comments
 (0)