8000 Merge pull request #71 from clue-labs/delay-v3 · reactphp/async@e623a64 · GitHub
[go: up one dir, main page]

Skip to content

Commit e623a64

Browse files
authored
Merge pull request #71 from clue-labs/delay-v3
[3.x] Add new `delay()` function to delay program execution
2 parents a96bb27 + cd0a864 commit e623a64

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ an event loop, it can be used with this library.
1919
* [Usage](#usage)
2020
* [await()](#await)
2121
* [coroutine()](#coroutine)
22+
* [delay()](#delay)
2223
* [parallel()](#parallel)
2324
* [series()](#series)
2425
* [waterfall()](#waterfall)
@@ -206,6 +207,74 @@ $promise->then(function (int $bytes) {
206207
});
207208
```
208209

210+
### delay()
211+
212+
The `delay(float $seconds): void` function can be used to
213+
delay program execution for duration given in `$seconds`.
214+
215+
```php
216+
React\Async\delay($seconds);
217+
```
218+
219+
This function will only return after the given number of `$seconds` have
220+
elapsed. If there are no other events attached to this loop, it will behave
221+
similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
222+
223+
```php
224+
echo 'a';
225+
React\Async\delay(1.0);
226+
echo 'b';
227+
228+
// prints "a" at t=0.0s
229+
// prints "b" at t=1.0s
230+
```
231+
232+
Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
233+
this function may not necessarily halt execution of the entire process thread.
234+
Instead, it allows the event loop to run any other events attached to the
235+
same loop until the delay returns:
236+
237+
```php
238+
echo 'a';
239+
Loop::addTimer(1.0, function () {
240+
echo 'b';
241+
});
242+
React\Async\delay(3.0);
243+
echo 'c';
8000 244+
245+
// prints "a" at t=0.0s
246+
// prints "b" at t=1.0s
247+
// prints "c" at t=3.0s
248+
```
249+
250+
This behavior is especially useful if you want to delay the program execution
251+
of a particular routine, such as when building a simple polling or retry
252+
mechanism:
253+
254+
```php
255+
try {
256+
something();
257+
} catch (Throwable $e) {
258+
// in case of error, retry after a short delay
259+
React\Async\delay(1.0);
260+
something();
261+
}
262+
```
263+
264+
Because this function only returns after some time has passed, it can be
265+
considered *blocking* from the perspective of the calling code. While the
266+
delay is running, this function will assume control over the event loop.
267+
Internally, it will `run()` the [default loop](https://github.com/reactphp/event-loop#loop)
268+
until the delay returns and then calls `stop()` to terminate execution of the
269+
loop. This means this function is more suited for short-lived promise executions
270+
when using promise-based APIs is not feasible. For long-running applications,
271+
using promise-based APIs by leveraging chained `then()` calls is usually preferable.
272+
273+
Internally, the `$seconds` argument will be used as a timer for the loop so that
274+
it keeps running until this timer triggers. This implies that if you pass a
275+
really small (or negative) value, it will still start a timer and will thus
276+
trigger at the earliest possible time in the future.
277+
209278
### parallel()
210279

211280
The `parallel(iterable<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
use function React\Promise\reject;
910
use function React\Promise\resolve;
@@ -101,6 +102,85 @@ function ($error) use (&$exception, &$rejected, &$wait, &$loopStarted) {
101102
}
102103

103104

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

tests/DelayTest.php

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

0 commit comments

Comments
 (0)
0