Skip to content

Commit 3ae32f1

Browse files
authored
Merge pull request #72 from clue-labs/delay-v2
[2.x] Add new `delay()` function to delay program execution
2 parents 25487af + 9dd30fc commit 3ae32f1

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ an event loop, it can be used with this library.
1818

1919
* [Usage](#usage)
2020
* [await()](#await)
21+
* [delay()](#delay)
2122
* [parallel()](#parallel)
2223
* [series()](#series)
2324
* [waterfall()](#waterfall)
@@ -90,6 +91,74 @@ try {
9091
}
9192
```
9293

94+
### delay()
95+
96+
The `delay(float $seconds): void` function can be used to
97+
delay program execution for duration given in `$seconds`.
98+
99+
```php
100+
React\Async\delay($seconds);
101+
```
102+
103+
This function will only return after the given number of `$seconds` have
104+
elapsed. If there are no other events attached to this loop, it will behave
105+
similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
106+
107+
```php
108+
echo 'a';
109+
React\Async\delay(1.0);
110+
echo 'b';
111+
112+
// prints "a" at t=0.0s
113+
// prints "b" at t=1.0s
114+
```
115+
116+
Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
117+
this function may not necessarily halt execution of the entire process thread.
118+
Instead, it allows the event loop to run any other events attached to the
119+
same loop until the delay returns:
120+
121+
```php
122+
echo 'a';
123+
Loop::addTimer(1.0, function () {
124+
echo 'b';
125+
});
126+
React\Async\delay(3.0);
127+
echo 'c';
128+
129+
// prints "a" at t=0.0s
130+
// prints "b" at t=1.0s
131+
// prints "c" at t=3.0s
132+
```
133+
134+
This behavior is especially useful if you want to delay the program execution
135+
of a particular routine, such as when building a simple polling or retry
136+
mechanism:
137+
138+
```php
139+
try {
140+
something();
141+
} catch (Exception $e) {
142+
// in case of error, retry after a short delay
143+
React\Async\delay(1.0);
144+
something();
145+
}
146+
```
147+
148+
Because this function only returns after some time has passed, it can be
149+
considered *blocking* from the perspective of the calling code. While the
150+
delay is running, this function will assume control over the event loop.
151+
Internally, it will `run()` the [default loop](https://github.com/reactphp/event-loop#loop)
152+
until the delay returns and then calls `stop()` to terminate execution of the
153+
loop. This means this function is more suited for short-lived promise executions
154+
when using promise-based APIs is not feasible. For long-running applications,
155+
using promise-based APIs by leveraging chained `then()` calls is usually preferable.
156+
157+
Internally, the `$seconds` argument will be used as a timer for the loop so that
158+
it keeps running until this timer triggers. This implies that if you pass a
159+
really small (or negative) value, it will still start a timer and will thus
160+
trigger at the earliest possible time in the future.
161+
93162
### parallel()
94163

95164
The `parallel(array<callable():PromiseInterface<mixed,Exception>> $tasks): PromiseInterface<array<mixed>,Exception>` function can be used

src/functions.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use React\EventLoop\Loop;
66
use React\Promise\Deferred;
7+
use React\Promise\Promise;
78
use React\Promise\PromiseInterface;
89

910
/**
@@ -98,6 +99,85 @@ function ($error) use (&$exception, &$rejected, &$wait, &$loopStarted) {
9899
return $resolved;
99100
}
100101

102+
/**
103+
* Delay program execution for duration given in `$seconds`.
104+
*
105+
* ```php
106+
* React\Async\delay($seconds);
107+
* ```
108+
*
109+
* This function will only return after the given number of `$seconds` have
110+
* elapsed. If there are no other events attached to this loop, it will behave
111+
* similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
112+
*
113+
* ```php
114+
* echo 'a';
115+
* React\Async\delay(1.0);
116+
* echo 'b';
117+
*
118+
* // prints "a" at t=0.0s
119+
* // prints "b" at t=1.0s
120+
* ```
121+
*
122+
* Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
123+
* this function may not necessarily halt execution of the entire process thread.
124+
* Instead, it allows the event loop to run any other events attached to the
125+
* same loop until the delay returns:
126+
*
127+
* ```php
128+
* echo 'a';
129+
* Loop::addTimer(1.0, function () {
130+
* echo 'b';
131+
* });
132+
* React\Async\delay(3.0);
133+
* echo 'c';
134+
*
135+
* // prints "a" at t=0.0s
136+
* // prints "b" at t=1.0s
137+
* // prints "c" at t=3.0s
138+
* ```
139+
*
140+
* This behavior is especially useful if you want to delay the program execution
141+
* of a particular routine, such as when building a simple polling or retry
142+
* mechanism:
143+
*
144+
* ```php
145+
* try {
146+
* something();
147+
* } catch (Exception $e) {
148+
* // in case of error, retry after a short delay
149+
* React\Async\delay(1.0);
150+
* something();
151+
* }
152+
* ```
153+
*
154+
* Because this function only returns after some time has passed, it can be
155+
* considered *blocking* from the perspective of the calling code. While the
156+
* delay is running, this function will assume control over the event loop.
157+
* Internally, it will `run()` the [default loop](https://github.com/reactphp/event-loop#loop)
158+
* until the delay returns and then calls `stop()` to terminate execution of the
159+
* loop. This means this function is more suited for short-lived promise executions
160+
* when using promise-based APIs is not feasible. For long-running applications,
161+
* using promise-based APIs by leveraging chained `then()` calls is usually preferable.
162+
*
163+
* Internally, the `$seconds` argument will be used as a timer for the loop so that
164+
* it keeps running until this timer triggers. This implies that if you pass a
165+
* really small (or negative) value, it will still start a timer and will thus
166+
* trigger at the earliest possible time in the future.
167+
*
168+
* @param float $seconds
169+
* @return void
170+
* @uses await()
171+
*/
172+
function delay($seconds)
173+
{
174+
await(new Promise(function ($resolve) use ($seconds) {
175+
Loop::addTimer($seconds, function () use ($resolve) {
176+
$resolve(null);
177+
});
178+
}));
179+
}
180+
101181
/**
102182
* @param array<callable():PromiseInterface<mixed,Exception>> $tasks
103183
* @return PromiseInterface<array<mixed>,Exception>

tests/DelayTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace React\Tests\Async;
4+
5+
use React;
6+
use React\EventLoop\Loop;
7+
8+
class DelayTest extends TestCase
9+
{
10+
public function testDelayBlocksForGivenPeriod()
11+
{
12+
$time = microtime(true);
13+
React\Async\delay(0.02);
14+
$time = microtime(true) - $time;
15+
16+
if (method_exists($this, 'assertEqualsWithDelta')) {
17+
// PHPUnit 7+
18+
$this->assertEqualsWithDelta(0.02, $time, 0.01);
19+
} else {
20+
// legacy PHPUnit
21+
$this->assertEquals(0.02, $time, '', 0.01);
22+
}
23+
}
24+
25+
public function testDelaySmallPeriodBlocksForCloseToZeroSeconds()
26+
{
27+
$time = microtime(true);
28+
React\Async\delay(0.000001);
29+
$time = microtime(true) - $time;
30+
31+
$this->assertLessThan(0.01, $time);
32+
}
33+
34+
public function testDelayNegativePeriodBlocksForCloseToZeroSeconds()
35+
{
36+
$time = microtime(true);
37+
React\Async\delay(-1);
38+
$time = microtime(true) - $time;
39+
40+
$this->assertLessThan(0.01, $time);
41+
}
42+
43+
public function testDelayRunsOtherEventsWhileWaiting()
44+
{
45+
$buffer = 'a';
46+
Loop::addTimer(0.001, function () use (&$buffer) {
47+
$buffer .= 'c';
48+
});
49+
$buffer .= 'b';
50+
React\Async\delay(0.002);
51+
$buffer .= 'd';
52+
53+
$this->assertEquals('abcd', $buffer);
54+
}
55+
}

0 commit comments

Comments
 (0)