Subversion Repositories qbpwcf-lib(archive)

Rev

Rev 915 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 liveuser 1
# PromiseTimer
2
 
3
[![Build Status](https://travis-ci.org/reactphp/promise-timer.svg?branch=master)](https://travis-ci.org/reactphp/promise-timer)
4
 
5
A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/).
6
 
7
**Table of contents**
8
 
9
* [Usage](#usage)
10
  * [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)
16
  * [resolve()](#resolve)
17
    * [Resolve cancellation](#resolve-cancellation)
18
  * [reject()](#reject)
19
    * [Reject cancellation](#reject-cancellation)
20
  * [TimeoutException](#timeoutexception)
21
* [Install](#install)
22
* [Tests](#tests)
23
* [License](#license)
24
 
25
## Usage
26
 
27
This lightweight library consists only of a few simple functions.
28
All functions reside under the `React\Promise\Timer` namespace.
29
 
30
The below examples assume you use an import statement similar to this:
31
 
32
```php
33
use React\Promise\Timer;
34
 
35
Timer\timeout(…);
36
```
37
 
38
Alternatively, you can also refer to them with their fully-qualified name:
39
 
40
```php
41
\React\Promise\Timer\timeout(…);
42
```
43
 
44
### timeout()
45
 
46
The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` function
47
can be used to *cancel* operations that take *too long*.
48
You need to pass in an input `$promise` that represents a pending operation and timeout parameters.
49
It returns a new `Promise` with the following resolution behavior:
50
 
51
* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.
52
* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.
53
* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).
54
 
55
Internally, the given `$time` value will be used to start a timer that will
56
*cancel* the pending operation once it triggers.
57
This implies that if you pass a really small (or negative) value, it will still
58
start a timer and will thus trigger at the earliest possible time in the future.
59
 
60
If the input `$promise` is already settled, then the resulting promise will
61
resolve or reject immediately without starting a timer at all.
62
 
63
A common use case for handling only resolved values looks like this:
64
 
65
```php
66
$promise = accessSomeRemoteResource();
67
Timer\timeout($promise, 10.0, $loop)->then(function ($value) {
68
    // the operation finished within 10.0 seconds
69
});
70
```
71
 
72
A more complete example could look like this:
73
 
74
```php
75
$promise = accessSomeRemoteResource();
76
Timer\timeout($promise, 10.0, $loop)->then(
77
    function ($value) {
78
        // the operation finished within 10.0 seconds
79
    },
80
    function ($error) {
81
        if ($error instanceof Timer\TimeoutException) {
82
            // the operation has failed due to a timeout
83
        } else {
84
            // the input operation has failed due to some other error
85
        }
86
    }
87
);
88
```
89
 
90
Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up:
91
 
92
```php
93
Timer\timeout($promise, 10.0, $loop)
94
    ->then(function ($value) {
95
        // the operation finished within 10.0 seconds
96
    })
97
    ->otherwise(function (Timer\TimeoutException $error) {
98
        // the operation has failed due to a timeout
99
    })
100
    ->otherwise(function ($error) {
101
        // the input operation has failed due to some other error
102
    })
103
;
104
```
105
 
106
#### Timeout cancellation
107
 
108
As discussed above, the [`timeout()`](#timeout) function will *cancel* the
109
underlying operation if it takes *too long*.
110
This means that you can be sure the resulting promise will then be rejected
111
with a [`TimeoutException`](#timeoutexception).
112
 
113
However, what happens to the underlying input `$promise` is a bit more tricky:
114
Once the timer fires, we will try to call
115
[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)
116
on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).
117
 
118
This means that it's actually up the input `$promise` to handle
119
[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).
120
 
121
* A common use case involves cleaning up any resources like open network sockets or
122
  file handles or terminating external processes or timers.
123
 
124
* If the given input `$promise` does not support cancellation, then this is a NO-OP.
125
  This means that while the resulting promise will still be rejected, the underlying
126
  input `$promise` may still be pending and can hence continue consuming resources.
127
 
128
See the following chapter for more details on the cancellation handler.
129
 
130
#### Cancellation handler
131
 
132
For example, an implementation for the above operation could look like this:
133
 
134
```php
135
function accessSomeRemoteResource()
136
{
137
    return new Promise(
138
        function ($resolve, $reject) use (&$socket) {
139
            // this will be called once the promise is created
140
            // a common use case involves opening any resources and eventually resolving
141
            $socket = createSocket();
142
            $socket->on('data', function ($data) use ($resolve) {
143
                $resolve($data);
144
            });
145
        },
146
        function ($resolve, $reject) use (&$socket) {
147
            // this will be called once calling `cancel()` on this promise
148
            // a common use case involves cleaning any resources and then rejecting
149
            $socket->close();
150
            $reject(new \RuntimeException('Operation cancelled'));
151
        }
152
    );
153
}
154
```
155
 
156
In this example, calling `$promise->cancel()` will invoke the registered cancellation
157
handler which then closes the network socket and rejects the `Promise` instance.
158
 
159
If no cancellation handler is passed to the `Promise` constructor, then invoking
160
its `cancel()` method it is effectively a NO-OP.
161
This means that it may still be pending and can hence continue consuming resources.
162
 
163
For more details on the promise cancellation, please refer to the
164
[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).
165
 
166
#### Input cancellation
167
 
168
Irrespective of the timeout handling, you can also explicitly `cancel()` the
169
input `$promise` at any time.
170
This means that the `timeout()` handling does not affect cancellation of the
171
input `$promise`, as demonstrated in the following example:
172
 
173
```php
174
$promise = accessSomeRemoteResource();
175
$timeout = Timer\timeout($promise, 10.0, $loop);
176
 
177
$promise->cancel();
178
```
179
 
180
The registered [cancellation handler](#cancellation-handler) is responsible for
181
handling the `cancel()` call:
182
 
183
* A described above, a common use involves resource cleanup and will then *reject*
184
  the `Promise`.
185
  If the input `$promise` is being rejected, then the timeout will be aborted
186
  and the resulting promise will also be rejected.
187
* If the input `$promise` is still pending, then the timout will continue
188
  running until the timer expires.
189
  The same happens if the input `$promise` does not register a
190
  [cancellation handler](#cancellation-handler). 
191
 
192
#### Output cancellation
193
 
194
Similarily, you can also explicitly `cancel()` the resulting promise like this:
195
 
196
```php
197
$promise = accessSomeRemoteResource();
198
$timeout = Timer\timeout($promise, 10.0, $loop);
199
 
200
$timeout->cancel();
201
```
202
 
203
Note how this looks very similar to the above [input cancellation](#input-cancellation)
204
example. Accordingly, it also behaves very similar.
205
 
206
Calling `cancel()` on the resulting promise will merely try
207
to `cancel()` the input `$promise`.
208
This means that we do not take over responsibility of the outcome and it's
209
entirely up to the input `$promise` to handle cancellation support.
210
 
211
The registered [cancellation handler](#cancellation-handler) is responsible for
212
handling the `cancel()` call:
213
 
214
* As described above, a common use involves resource cleanup and will then *reject*
215
  the `Promise`.
216
  If the input `$promise` is being rejected, then the timeout will be aborted
217
  and the resulting promise will also be rejected.
218
* If the input `$promise` is still pending, then the timout will continue
219
  running until the timer expires.
220
  The same happens if the input `$promise` does not register a
221
  [cancellation handler](#cancellation-handler). 
222
 
223
To re-iterate, note that calling `cancel()` on the resulting promise will merely
224
try to cancel the input `$promise` only.
225
It is then up to the cancellation handler of the input promise to settle the promise.
226
If the input promise is still pending when the timeout occurs, then the normal
227
[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejecting
228
the output promise with a [`TimeoutException`](#timeoutexception).
229
 
230
This is done for consistency with the [timeout cancellation](#timeout-cancellation)
231
handling and also because it is assumed this is often used like this:
232
 
233
```php
234
$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop);
235
 
236
$timeout->cancel();
237
```
238
 
239
As described above, this example works as expected and cleans up any resources
240
allocated for the input `$promise`.
241
 
242
Note that if the given input `$promise` does not support cancellation, then this
243
is a NO-OP.
244
This means that while the resulting promise will still be rejected after the
245
timeout, the underlying input `$promise` may still be pending and can hence
246
continue consuming resources.
247
 
248
#### Collections
249
 
250
If you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:
251
 
252
```php
253
$promises = array(
254
    accessSomeRemoteResource(),
255
    accessSomeRemoteResource(),
256
    accessSomeRemoteResource()
257
);
258
 
259
$promise = \React\Promise\all($promises);
260
 
261
Timer\timeout($promise, 10, $loop)->then(function ($values) {
262
    // *all* promises resolved
263
});
264
```
265
 
266
The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.
267
 
268
For more details on the promise primitives, please refer to the
269
[Promise documentation](https://github.com/reactphp/promise#functions).
270
 
271
### resolve()
272
 
273
The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise that
274
resolves in `$time` seconds with the `$time` as the fulfillment value.
275
 
276
```php
277
Timer\resolve(1.5, $loop)->then(function ($time) {
278
    echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;
279
});
280
```
281
 
282
Internally, the given `$time` value will be used to start a timer that will
283
resolve the promise once it triggers.
284
This implies that if you pass a really small (or negative) value, it will still
285
start a timer and will thus trigger at the earliest possible time in the future.
286
 
287
#### Resolve cancellation
288
 
289
You can explicitly `cancel()` the resulting timer promise at any time:
290
 
291
```php
292
$timer = Timer\resolve(2.0, $loop);
293
 
294
$timer->cancel();
295
```
296
 
297
This will abort the timer and *reject* with a `RuntimeException`.
298
 
299
### reject()
300
 
301
The `reject($time, LoopInterface $loop)` function can be used to create a new Promise
302
which rejects in `$time` seconds with a `TimeoutException`.
303
 
304
```php
305
Timer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {
306
    echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;
307
});
308
```
309
 
310
Internally, the given `$time` value will be used to start a timer that will
311
reject the promise once it triggers.
312
This implies that if you pass a really small (or negative) value, it will still
313
start a timer and will thus trigger at the earliest possible time in the future.
314
 
315
This function complements the [`resolve()`](#resolve) function
316
and can be used as a basic building block for higher-level promise consumers.
317
 
318
#### Reject cancellation
319
 
320
You can explicitly `cancel()` the resulting timer promise at any time:
321
 
322
```php
323
$timer = Timer\reject(2.0, $loop);
324
 
325
$timer->cancel();
326
```
327
 
328
This will abort the timer and *reject* with a `RuntimeException`.
329
 
330
### TimeoutException
331
 
332
The `TimeoutException` extends PHP's built-in `RuntimeException`.
333
 
334
The `getTimeout()` method can be used to get the timeout value in seconds.
335
 
336
## Install
337
 
338
The recommended way to install this library is [through Composer](https://getcomposer.org).
339
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
340
 
341
This project follows [SemVer](https://semver.org/).
342
This will install the latest supported version:
343
 
344
```bash
345
$ composer require react/promise-timer:^1.6
346
```
347
 
348
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
349
 
350
This project aims to run on any platform and thus does not require any PHP
351
extensions and supports running on legacy PHP 5.3 through current PHP 7+ and
352
HHVM.
353
It's *highly recommended to use PHP 7+* for this project.
354
 
355
## Tests
356
 
357
To run the test suite, you first need to clone this repo and then install all
358
dependencies [through Composer](https://getcomposer.org):
359
 
360
```bash
361
$ composer install
362
```
363
 
364
To run the test suite, go to the project root and run:
365
 
366
```bash
367
$ php vendor/bin/phpunit
368
```
369
 
370
## License
371
 
372
MIT, see [LICENSE file](LICENSE).