Skip to content

Commit 099700b

Browse files
authored
Merge pull request #49 from clue-labs/docs
Improve API documentation and add parameter types and return types
2 parents 7097b88 + 62bef4d commit 099700b

File tree

3 files changed

+274
-176
lines changed

3 files changed

+274
-176
lines changed

README.md

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

99
* [Usage](#usage)
1010
* [timeout()](#timeout)
11-
* [Timeout cancellation](#timeout-cancellation)
12-
* [Cancellation handler](#cancellation-handler)
13-
* [Input cancellation](#input-cancellation)
14-
* [Output cancellation](#output-cancellation)
15-
* [Collections](#collections)
1611
* [resolve()](#resolve)
17-
* [Resolve cancellation](#resolve-cancellation)
1812
* [reject()](#reject)
19-
* [Reject cancellation](#reject-cancellation)
2013
* [TimeoutException](#timeoutexception)
14+
* [getTimeout()](#gettimeout)
2115
* [Install](#install)
2216
* [Tests](#tests)
2317
* [License](#license)
@@ -51,19 +45,26 @@ Timer\timeout(…);
5145

5246
### timeout()
5347

54-
The `timeout(PromiseInterface $promise, $time, LoopInterface $loop = null)` function
55-
can be used to *cancel* operations that take *too long*.
56-
You need to pass in an input `$promise` that represents a pending operation and timeout parameters.
57-
It returns a new `Promise` with the following resolution behavior:
48+
The `timeout(PromiseInterface<mixed, Exception|mixed> $promise, float $time, ?LoopInterface $loop = null): PromiseInterface<mixed, TimeoutException|Exception|mixed>` function can be used to
49+
cancel operations that take *too long*.
5850

59-
* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.
60-
* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
61-
* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
51+
You need to pass in an input `$promise` that represents a pending operation
52+
and timeout parameters. It returns a new promise with the following
53+
resolution behavior:
54+
55+
- If the input `$promise` resolves before `$time` seconds, resolve the
56+
resulting promise with its fulfillment value.
57+
58+
- If the input `$promise` rejects before `$time` seconds, reject the
59+
resulting promise with its rejection value.
60+
61+
- If the input `$promise` does not settle before `$time` seconds, *cancel*
62+
the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
6263

6364
Internally, the given `$time` value will be used to start a timer that will
64-
*cancel* the pending operation once it triggers.
65-
This implies that if you pass a really small (or negative) value, it will still
66-
start a timer and will thus trigger at the earliest possible time in the future.
65+
*cancel* the pending operation once it triggers. This implies that if you
66+
pass a really small (or negative) value, it will still start a timer and will
67+
thus trigger at the earliest possible time in the future.
6768

6869
If the input `$promise` is already settled, then the resulting promise will
6970
resolve or reject immediately without starting a timer at all.
@@ -117,95 +118,25 @@ React\Promise\Timer\timeout($promise, 10.0)
117118
;
118119
```
119120

120-
#### Timeout cancellation
121-
122-
As discussed above, the [`timeout()`](#timeout) function will *cancel* the
123-
underlying operation if it takes *too long*.
124-
This means that you can be sure the resulting promise will then be rejected
125-
with a [`TimeoutException`](#timeoutexception).
126-
127-
However, what happens to the underlying input `$promise` is a bit more tricky:
128-
Once the timer fires, we will try to call
129-
[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)
130-
on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).
131-
132-
This means that it's actually up the input `$promise` to handle
133-
[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).
134-
135-
* A common use case involves cleaning up any resources like open network sockets or
136-
file handles or terminating external processes or timers.
137-
138-
* If the given input `$promise` does not support cancellation, then this is a NO-OP.
139-
This means that while the resulting promise will still be rejected, the underlying
140-
input `$promise` may still be pending and can hence continue consuming resources.
141-
142-
See the following chapter for more details on the cancellation handler.
143-
144-
#### Cancellation handler
145-
146-
For example, an implementation for the above operation could look like this:
147-
148-
```php
149-
function accessSomeRemoteResource()
150-
{
151-
return new Promise(
152-
function ($resolve, $reject) use (&$socket) {
153-
// this will be called once the promise is created
154-
// a common use case involves opening any resources and eventually resolving
155-
$socket = createSocket();
156-
$socket->on('data', function ($data) use ($resolve) {
157-
$resolve($data);
158-
});
159-
},
160-
function ($resolve, $reject) use (&$socket) {
161-
// this will be called once calling `cancel()` on this promise
162-
// a common use case involves cleaning any resources and then rejecting
163-
$socket->close();
164-
$reject(new \RuntimeException('Operation cancelled'));
165-
}
166-
);
167-
}
168-
```
169-
170-
In this example, calling `$promise->cancel()` will invoke the registered cancellation
171-
handler which then closes the network socket and rejects the `Promise` instance.
121+
As discussed above, the [`timeout()`](#timeout) function will take care of
122+
the underlying operation if it takes *too long*. In this case, you can be
123+
sure the resulting promise will always be rejected with a
124+
[`TimeoutException`](#timeoutexception). On top of this, the function will
125+
try to *cancel* the underlying operation. Responsibility for this
126+
cancellation logic is left up to the underlying operation.
172127

173-
If no cancellation handler is passed to the `Promise` constructor, then invoking
174-
its `cancel()` method it is effectively a NO-OP.
175-
This means that it may still be pending and can hence continue consuming resources.
128+
- A common use case involves cleaning up any resources like open network
129+
sockets or file handles or terminating external processes or timers.
176130

177-
For more details on the promise cancellation, please refer to the
178-
[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
179-
180-
#### Input cancellation
131+
- If the given input `$promise` does not support cancellation, then this is a
132+
NO-OP. This means that while the resulting promise will still be rejected,
133+
the underlying input `$promise` may still be pending and can hence continue
134+
consuming resources
181135

182-
Irrespective of the timeout handling, you can also explicitly `cancel()` the
183-
input `$promise` at any time.
184-
This means that the `timeout()` handling does not affect cancellation of the
185-
input `$promise`, as demonstrated in the following example:
186-
187-
```php
188-
$promise = accessSomeRemoteResource();
189-
$timeout = React\Promise\Timer\timeout($promise, 10.0);
190-
191-
$promise->cancel();
192-
```
193-
194-
The registered [cancellation handler](#cancellation-handler) is responsible for
195-
handling the `cancel()` call:
196-
197-
* A described above, a common use involves resource cleanup and will then *reject*
198-
the `Promise`.
199-
If the input `$promise` is being rejected, then the timeout will be aborted
200-
and the resulting promise will also be rejected.
201-
* If the input `$promise` is still pending, then the timout will continue
202-
running until the timer expires.
203-
The same happens if the input `$promise` does not register a
204-
[cancellation handler](#cancellation-handler).
205-
206-
#### Output cancellation
207-
208-
Similarily, you can also explicitly `cancel()` the resulting promise like this:
136+
On top of this, the returned promise is implemented in such a way that it can
137+
be cancelled when it is still pending. Cancelling a pending promise will
138+
cancel the underlying operation. As discussed above, responsibility for this
139+
cancellation logic is left up to the underlying operation.
209140

210141
```php
211142
$promise = accessSomeRemoteResource();
@@ -214,54 +145,11 @@ $timeout = React\Promise\Timer\timeout($promise, 10.0);
214145
$timeout->cancel();
215146
```
216147

217-
Note how this looks very similar to the above [input cancellation](#input-cancellation)
218-
example. Accordingly, it also behaves very similar.
219-
220-
Calling `cancel()` on the resulting promise will merely try
221-
to `cancel()` the input `$promise`.
222-
This means that we do not take over responsibility of the outcome and it's
223-
entirely up to the input `$promise` to handle cancellation support.
224-
225-
The registered [cancellation handler](#cancellation-handler) is responsible for
226-
handling the `cancel()` call:
227-
228-
* As described above, a common use involves resource cleanup and will then *reject*
229-
the `Promise`.
230-
If the input `$promise` is being rejected, then the timeout will be aborted
231-
and the resulting promise will also be rejected.
232-
* If the input `$promise` is still pending, then the timout will continue
233-
running until the timer expires.
234-
The same happens if the input `$promise` does not register a
235-
[cancellation handler](#cancellation-handler).
236-
237-
To re-iterate, note that calling `cancel()` on the resulting promise will merely
238-
try to cancel the input `$promise` only.
239-
It is then up to the cancellation handler of the input promise to settle the promise.
240-
If the input promise is still pending when the timeout occurs, then the normal
241-
[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting
242-
the output promise with a [`TimeoutException`](#timeoutexception).
243-
244-
This is done for consistency with the [timeout cancellation](#timeout-cancellation)
245-
handling and also because it is assumed this is often used like this:
246-
247-
```php
248-
$timeout = React\Promise\Timer\timeout(accessSomeRemoteResource(), 10.0);
249-
250-
$timeout->cancel();
251-
```
252-
253-
As described above, this example works as expected and cleans up any resources
254-
allocated for the input `$promise`.
255-
256-
Note that if the given input `$promise` does not support cancellation, then this
257-
is a NO-OP.
258-
This means that while the resulting promise will still be rejected after the
259-
timeout, the underlying input `$promise` may still be pending and can hence
260-
continue consuming resources.
261-
262-
#### Collections
148+
For more details on the promise cancellation, please refer to the
149+
[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
263150

264-
If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:
151+
If you want to wait for multiple promises to resolve, you can use the normal
152+
promise primitives like this:
265153

266154
```php
267155
$promises = array(
@@ -270,22 +158,23 @@ $promises = array(
270158
accessSomeRemoteResource()
271159
);
272160

273-
$promise = \React\Promise\all($promises);
161+
$promise = React\Promise\all($promises);
274162

275163
React\Promise\Timer\timeout($promise, 10)->then(function ($values) {
276164
// *all* promises resolved
277165
});
278166
```
279167

280-
The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.
168+
The applies to all promise collection primitives alike, i.e. `all()`,
169+
`race()`, `any()`, `some()` etc.
281170

282171
For more details on the promise primitives, please refer to the
283172
[Promise documentation](https://github.com/reactphp/promise#functions).
284173

285174
### resolve()
286175

287-
The `resolve($time, LoopInterface $loop = null)` function can be used to create a new Promise that
288-
resolves in `$time` seconds with the `$time` as the fulfillment value.
176+
The `resolve(float $time, ?LoopInterface $loop = null): PromiseInterface<float, RuntimeException>` function can be used to
177+
create a new promise that resolves in `$time` seconds with the `$time` as the fulfillment value.
289178

290179
```php
291180
React\Promise\Timer\resolve(1.5)->then(function ($time) {
@@ -294,32 +183,30 @@ React\Promise\Timer\resolve(1.5)->then(function ($time) {
294183
```
295184

296185
Internally, the given `$time` value will be used to start a timer that will
297-
resolve the promise once it triggers.
298-
This implies that if you pass a really small (or negative) value, it will still
299-
start a timer and will thus trigger at the earliest possible time in the future.
186+
resolve the promise once it triggers. This implies that if you pass a really
187+
small (or negative) value, it will still start a timer and will thus trigger
188+
at the earliest possible time in the future.
300189

301190
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
302191
pass the event loop instance to use. You can use a `null` value here in order to
303192
use the [default loop](https://github.com/reactphp/event-loop#loop). This value
304193
SHOULD NOT be given unless you're sure you want to explicitly use a given event
305194
loop instance.
306195

307-
#### Resolve cancellation
308-
309-
You can explicitly `cancel()` the resulting timer promise at any time:
196+
The returned promise is implemented in such a way that it can be cancelled
197+
when it is still pending. Cancelling a pending promise will reject its value
198+
with a `RuntimeException` and clean up any pending timers.
310199

311200
```php
312201
$timer = React\Promise\Timer\resolve(2.0);
313202

314203
$timer->cancel();
315204
```
316205

317-
This will abort the timer and *reject* with a `RuntimeException`.
318-
319206
### reject()
320207

321-
The `reject($time, LoopInterface $loop = null)` function can be used to create a new Promise
322-
which rejects in `$time` seconds with a `TimeoutException`.
208+
The `reject(float $time, ?LoopInterface $loop = null): PromiseInterface<void, TimeoutException|RuntimeException>` function can be used to
209+
create a new promise which rejects in `$time` seconds with a `TimeoutException`.
323210

324211
```php
325212
React\Promise\Timer\reject(2.0)->then(null, function (React\Promise\Timer\TimeoutException $e) {
@@ -328,36 +215,35 @@ React\Promise\Timer\reject(2.0)->then(null, function (React\Promise\Timer\Timeou
328215
```
329216

330217
Internally, the given `$time` value will be used to start a timer that will
331-
reject the promise once it triggers.
332-
This implies that if you pass a really small (or negative) value, it will still
333-
start a timer and will thus trigger at the earliest possible time in the future.
218+
reject the promise once it triggers. This implies that if you pass a really
219+
small (or negative) value, it will still start a timer and will thus trigger
220+
at the earliest possible time in the future.
334221

335222
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
336223
pass the event loop instance to use. You can use a `null` value here in order to
337224
use the [default loop](https://github.com/reactphp/event-loop#loop). This value
338225
SHOULD NOT be given unless you're sure you want to explicitly use a given event
339226
loop instance.
340227

341-
This function complements the [`resolve()`](#resolve) function
342-
and can be used as a basic building block for higher-level promise consumers.
343-
344-
#### Reject cancellation
345-
346-
You can explicitly `cancel()` the resulting timer promise at any time:
228+
The returned promise is implemented in such a way that it can be cancelled
229+
when it is still pending. Cancelling a pending promise will reject its value
230+
with a `RuntimeException` and clean up any pending timers.
347231

348232
```php
349233
$timer = React\Promise\Timer\reject(2.0);
350234

351235
$timer->cancel();
352236
```
353237

354-
This will abort the timer and *reject* with a `RuntimeException`.
355-
356238
### TimeoutException
357239

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

360-
The `getTimeout()` method can be used to get the timeout value in seconds.
242+
243+
#### getTimeout()
244+
245+
The `getTimeout(): float` method can be used to
246+
get the timeout value in seconds.
361247

362248
## Install
363249

src/TimeoutException.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,27 @@
66

77
class TimeoutException extends RuntimeException
88
{
9+
/** @var float */
910
private $timeout;
1011

12+
/**
13+
* @param float $timeout
14+
* @param ?string $message
15+
* @param ?int $code
16+
* @param null|\Exception|\Throwable $previous
17+
*/
1118
public function __construct($timeout, $message = null, $code = null, $previous = null)
1219
{
1320
parent::__construct($message, $code, $previous);
1421

1522
$this->timeout = $timeout;
1623
}
1724

25+
/**
26+
* Get the timeout value in seconds.
27+
*
28+
* @return float
29+
*/
1830
public function getTimeout()
1931
{
2032
return $this->timeout;

0 commit comments

Comments
 (0)