Rev 915 | Blame | Compare with Previous | Last modification | View Log | RSS feed
# PromiseTimer[](https://travis-ci.org/reactphp/promise-timer)A trivial implementation of timeouts for `Promise`s, built on top of [ReactPHP](https://reactphp.org/).**Table of contents*** [Usage](#usage)* [timeout()](#timeout)* [Timeout cancellation](#timeout-cancellation)* [Cancellation handler](#cancellation-handler)* [Input cancellation](#input-cancellation)* [Output cancellation](#output-cancellation)* [Collections](#collections)* [resolve()](#resolve)* [Resolve cancellation](#resolve-cancellation)* [reject()](#reject)* [Reject cancellation](#reject-cancellation)* [TimeoutException](#timeoutexception)* [Install](#install)* [Tests](#tests)* [License](#license)## UsageThis lightweight library consists only of a few simple functions.All functions reside under the `React\Promise\Timer` namespace.The below examples assume you use an import statement similar to this:```phpuse React\Promise\Timer;Timer\timeout(…);```Alternatively, you can also refer to them with their fully-qualified name:```php\React\Promise\Timer\timeout(…);```### timeout()The `timeout(PromiseInterface $promise, $time, LoopInterface $loop)` functioncan be used to *cancel* operations that take *too long*.You need to pass in an input `$promise` that represents a pending operation and timeout parameters.It returns a new `Promise` with the following resolution behavior:* If the input `$promise` resolves before `$time` seconds, resolve the resulting promise with its fulfillment value.* If the input `$promise` rejects before `$time` seconds, reject the resulting promise with its rejection value.* If the input `$promise` does not settle before `$time` seconds, *cancel* the operation and reject the resulting promise with a [`TimeoutException`](#timeoutexception).Internally, the given `$time` value will be used to start a timer that will*cancel* the pending operation once it triggers.This implies that if you pass a really small (or negative) value, it will stillstart a timer and will thus trigger at the earliest possible time in the future.If the input `$promise` is already settled, then the resulting promise willresolve or reject immediately without starting a timer at all.A common use case for handling only resolved values looks like this:```php$promise = accessSomeRemoteResource();Timer\timeout($promise, 10.0, $loop)->then(function ($value) {// the operation finished within 10.0 seconds});```A more complete example could look like this:```php$promise = accessSomeRemoteResource();Timer\timeout($promise, 10.0, $loop)->then(function ($value) {// the operation finished within 10.0 seconds},function ($error) {if ($error instanceof Timer\TimeoutException) {// the operation has failed due to a timeout} else {// the input operation has failed due to some other error}});```Or if you're using [react/promise v2.2.0](https://github.com/reactphp/promise) or up:```phpTimer\timeout($promise, 10.0, $loop)->then(function ($value) {// the operation finished within 10.0 seconds})->otherwise(function (Timer\TimeoutException $error) {// the operation has failed due to a timeout})->otherwise(function ($error) {// the input operation has failed due to some other error});```#### Timeout cancellationAs discussed above, the [`timeout()`](#timeout) function will *cancel* theunderlying operation if it takes *too long*.This means that you can be sure the resulting promise will then be rejectedwith a [`TimeoutException`](#timeoutexception).However, what happens to the underlying input `$promise` is a bit more tricky:Once the timer fires, we will try to call[`$promise->cancel()`](https://github.com/reactphp/promise#cancellablepromiseinterfacecancel)on the input `$promise` which in turn invokes its [cancellation handler](#cancellation-handler).This means that it's actually up the input `$promise` to handle[cancellation support](https://github.com/reactphp/promise#cancellablepromiseinterface).* A common use case involves cleaning up any resources like open network sockets orfile handles or terminating external processes or timers.* If the given input `$promise` does not support cancellation, then this is a NO-OP.This means that while the resulting promise will still be rejected, the underlyinginput `$promise` may still be pending and can hence continue consuming resources.See the following chapter for more details on the cancellation handler.#### Cancellation handlerFor example, an implementation for the above operation could look like this:```phpfunction accessSomeRemoteResource(){return new Promise(function ($resolve, $reject) use (&$socket) {// this will be called once the promise is created// a common use case involves opening any resources and eventually resolving$socket = createSocket();$socket->on('data', function ($data) use ($resolve) {$resolve($data);});},function ($resolve, $reject) use (&$socket) {// this will be called once calling `cancel()` on this promise// a common use case involves cleaning any resources and then rejecting$socket->close();$reject(new \RuntimeException('Operation cancelled'));});}```In this example, calling `$promise->cancel()` will invoke the registered cancellationhandler which then closes the network socket and rejects the `Promise` instance.If no cancellation handler is passed to the `Promise` constructor, then invokingits `cancel()` method it is effectively a NO-OP.This means that it may still be pending and can hence continue consuming resources.For more details on the promise cancellation, please refer to the[Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface).#### Input cancellationIrrespective of the timeout handling, you can also explicitly `cancel()` theinput `$promise` at any time.This means that the `timeout()` handling does not affect cancellation of theinput `$promise`, as demonstrated in the following example:```php$promise = accessSomeRemoteResource();$timeout = Timer\timeout($promise, 10.0, $loop);$promise->cancel();```The registered [cancellation handler](#cancellation-handler) is responsible forhandling the `cancel()` call:* A described above, a common use involves resource cleanup and will then *reject*the `Promise`.If the input `$promise` is being rejected, then the timeout will be abortedand the resulting promise will also be rejected.* If the input `$promise` is still pending, then the timout will continuerunning until the timer expires.The same happens if the input `$promise` does not register a[cancellation handler](#cancellation-handler).#### Output cancellationSimilarily, you can also explicitly `cancel()` the resulting promise like this:```php$promise = accessSomeRemoteResource();$timeout = Timer\timeout($promise, 10.0, $loop);$timeout->cancel();```Note how this looks very similar to the above [input cancellation](#input-cancellation)example. Accordingly, it also behaves very similar.Calling `cancel()` on the resulting promise will merely tryto `cancel()` the input `$promise`.This means that we do not take over responsibility of the outcome and it'sentirely up to the input `$promise` to handle cancellation support.The registered [cancellation handler](#cancellation-handler) is responsible forhandling the `cancel()` call:* As described above, a common use involves resource cleanup and will then *reject*the `Promise`.If the input `$promise` is being rejected, then the timeout will be abortedand the resulting promise will also be rejected.* If the input `$promise` is still pending, then the timout will continuerunning until the timer expires.The same happens if the input `$promise` does not register a[cancellation handler](#cancellation-handler).To re-iterate, note that calling `cancel()` on the resulting promise will merelytry to cancel the input `$promise` only.It is then up to the cancellation handler of the input promise to settle the promise.If the input promise is still pending when the timeout occurs, then the normal[timeout cancellation](#timeout-cancellation) handling will trigger, effectively rejectingthe output promise with a [`TimeoutException`](#timeoutexception).This is done for consistency with the [timeout cancellation](#timeout-cancellation)handling and also because it is assumed this is often used like this:```php$timeout = Timer\timeout(accessSomeRemoteResource(), 10.0, $loop);$timeout->cancel();```As described above, this example works as expected and cleans up any resourcesallocated for the input `$promise`.Note that if the given input `$promise` does not support cancellation, then thisis a NO-OP.This means that while the resulting promise will still be rejected after thetimeout, the underlying input `$promise` may still be pending and can hencecontinue consuming resources.#### CollectionsIf you want to wait for multiple promises to resolve, you can use the normal promise primitives like this:```php$promises = array(accessSomeRemoteResource(),accessSomeRemoteResource(),accessSomeRemoteResource());$promise = \React\Promise\all($promises);Timer\timeout($promise, 10, $loop)->then(function ($values) {// *all* promises resolved});```The applies to all promise collection primitives alike, i.e. `all()`, `race()`, `any()`, `some()` etc.For more details on the promise primitives, please refer to the[Promise documentation](https://github.com/reactphp/promise#functions).### resolve()The `resolve($time, LoopInterface $loop)` function can be used to create a new Promise thatresolves in `$time` seconds with the `$time` as the fulfillment value.```phpTimer\resolve(1.5, $loop)->then(function ($time) {echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL;});```Internally, the given `$time` value will be used to start a timer that willresolve the promise once it triggers.This implies that if you pass a really small (or negative) value, it will stillstart a timer and will thus trigger at the earliest possible time in the future.#### Resolve cancellationYou can explicitly `cancel()` the resulting timer promise at any time:```php$timer = Timer\resolve(2.0, $loop);$timer->cancel();```This will abort the timer and *reject* with a `RuntimeException`.### reject()The `reject($time, LoopInterface $loop)` function can be used to create a new Promisewhich rejects in `$time` seconds with a `TimeoutException`.```phpTimer\reject(2.0, $loop)->then(null, function (TimeoutException $e) {echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL;});```Internally, the given `$time` value will be used to start a timer that willreject the promise once it triggers.This implies that if you pass a really small (or negative) value, it will stillstart a timer and will thus trigger at the earliest possible time in the future.This function complements the [`resolve()`](#resolve) functionand can be used as a basic building block for higher-level promise consumers.#### Reject cancellationYou can explicitly `cancel()` the resulting timer promise at any time:```php$timer = Timer\reject(2.0, $loop);$timer->cancel();```This will abort the timer and *reject* with a `RuntimeException`.### TimeoutExceptionThe `TimeoutException` extends PHP's built-in `RuntimeException`.The `getTimeout()` method can be used to get the timeout value in seconds.## InstallThe recommended way to install this library is [through Composer](https://getcomposer.org).[New to Composer?](https://getcomposer.org/doc/00-intro.md)This project follows [SemVer](https://semver.org/).This will install the latest supported version:```bash$ composer require react/promise-timer:^1.6```See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.This project aims to run on any platform and thus does not require any PHPextensions and supports running on legacy PHP 5.3 through current PHP 7+ andHHVM.It's *highly recommended to use PHP 7+* for this project.## TestsTo run the test suite, you first need to clone this repo and then install alldependencies [through Composer](https://getcomposer.org):```bash$ composer install```To run the test suite, go to the project root and run:```bash$ php vendor/bin/phpunit```## LicenseMIT, see [LICENSE file](LICENSE).