then(function ($value) { * // the operation finished within 10.0 seconds * }); * ``` * * A more complete example could look like this: * * ```php * $promise = accessSomeRemoteResource(); * React\Promise\Timer\timeout($promise, 10.0)->then( * function ($value) { * // the operation finished within 10.0 seconds * }, * function ($error) { * if ($error instanceof React\Promise\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 v3](https://github.com/reactphp/promise): * * ```php * React\Promise\Timer\timeout($promise, 10.0)->then(function ($value) { * // the operation finished within 10.0 seconds * })->catch(function (React\Promise\Timer\TimeoutException $error) { * // the operation has failed due to a timeout * })->catch(function (Throwable $error) { * // the input operation has failed due to some other error * }); * ``` * * As discussed above, the [`timeout()`](#timeout) function will take care of * the underlying operation if it takes *too long*. In this case, you can be * sure the resulting promise will always be rejected with a * [`TimeoutException`](#timeoutexception). On top of this, the function will * try to *cancel* the underlying operation. Responsibility for this * cancellation logic is left up to the underlying operation. * * - A common use case involves cleaning up any resources like open network * sockets or file 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 underlying input `$promise` may still be pending and can hence continue * consuming resources * * On top of this, the returned promise is implemented in such a way that it can * be cancelled when it is still pending. Cancelling a pending promise will * cancel the underlying operation. As discussed above, responsibility for this * cancellation logic is left up to the underlying operation. * * ```php * $promise = accessSomeRemoteResource(); * $timeout = React\Promise\Timer\timeout($promise, 10.0); * * $timeout->cancel(); * ``` * * For more details on the promise cancellation, please refer to the * [Promise documentation](https://github.com/reactphp/promise#cancellablepromiseinterface). * * If 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); * * React\Promise\Timer\timeout($promise, 10)->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). * * @param PromiseInterface $promise * @param float $time * @param ?LoopInterface $loop * @return PromiseInterface */ function timeout(PromiseInterface $promise, $time, LoopInterface $loop = null) { // cancelling this promise will only try to cancel the input promise, // thus leaving responsibility to the input promise. $canceller = null; if (\method_exists($promise, 'cancel')) { // pass promise by reference to clean reference after cancellation handler // has been invoked once in order to avoid garbage references in call stack. $canceller = function () use (&$promise) { $promise->cancel(); $promise = null; }; } if ($loop === null) { $loop = Loop::get(); } return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) { $timer = null; $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) { if ($timer) { $loop->cancelTimer($timer); } $timer = false; $resolve($v); }, function ($v) use (&$timer, $loop, $reject) { if ($timer) { $loop->cancelTimer($timer); } $timer = false; $reject($v); }); // promise already resolved => no need to start timer if ($timer === false) { return; } // start timeout timer which will cancel the input promise $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject) { $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds')); // try to invoke cancellation handler of input promise and then clean // reference in order to avoid garbage references in call stack. if (\method_exists($promise, 'cancel')) { $promise->cancel(); } $promise = null; }); }, $canceller); } /** * Create a new promise that resolves in `$time` seconds. * * ```php * React\Promise\Timer\sleep(1.5)->then(function () { * echo 'Thanks for waiting!' . PHP_EOL; * }); * ``` * * Internally, the given `$time` value will be used to start a timer that will * resolve the promise once it triggers. This implies that if you pass a really * small (or negative) value, it will still start a timer and will thus trigger * at the earliest possible time in the future. * * This function takes an optional `LoopInterface|null $loop` parameter that can be used to * pass the event loop instance to use. You can use a `null` value here in order to * use the [default loop](https://github.com/reactphp/event-loop#loop). This value * SHOULD NOT be given unless you're sure you want to explicitly use a given event * loop instance. * * The returned promise is implemented in such a way that it can be cancelled * when it is still pending. Cancelling a pending promise will reject its value * with a `RuntimeException` and clean up any pending timers. * * ```php * $timer = React\Promise\Timer\sleep(2.0); * * $timer->cancel(); * ``` * * @param float $time * @param ?LoopInterface $loop * @return PromiseInterface */ function sleep($time, LoopInterface $loop = null) { if ($loop === null) { $loop = Loop::get(); } $timer = null; return new Promise(function ($resolve) use ($loop, $time, &$timer) { // resolve the promise when the timer fires in $time seconds $timer = $loop->addTimer($time, function () use ($resolve) { $resolve(null); }); }, function () use (&$timer, $loop) { // cancelling this promise will cancel the timer, clean the reference // in order to avoid garbage references in call stack and then reject. $loop->cancelTimer($timer); $timer = null; throw new \RuntimeException('Timer cancelled'); }); } /** * [Deprecated] Create a new promise that resolves in `$time` seconds with the `$time` as the fulfillment value. * * ```php * React\Promise\Timer\resolve(1.5)->then(function ($time) { * echo 'Thanks for waiting ' . $time . ' seconds' . PHP_EOL; * }); * ``` * * Internally, the given `$time` value will be used to start a timer that will * resolve the promise once it triggers. This implies that if you pass a really * small (or negative) value, it will still start a timer and will thus trigger * at the earliest possible time in the future. * * This function takes an optional `LoopInterface|null $loop` parameter that can be used to * pass the event loop instance to use. You can use a `null` value here in order to * use the [default loop](https://github.com/reactphp/event-loop#loop). This value * SHOULD NOT be given unless you're sure you want to explicitly use a given event * loop instance. * * The returned promise is implemented in such a way that it can be cancelled * when it is still pending. Cancelling a pending promise will reject its value * with a `RuntimeException` and clean up any pending timers. * * ```php * $timer = React\Promise\Timer\resolve(2.0); * * $timer->cancel(); * ``` * * @param float $time * @param ?LoopInterface $loop * @return PromiseInterface * @deprecated 1.8.0 See `sleep()` instead * @see sleep() */ function resolve($time, LoopInterface $loop = null) { return sleep($time, $loop)->then(function() use ($time) { return $time; }); } /** * [Deprecated] Create a new promise which rejects in `$time` seconds with a `TimeoutException`. * * ```php * React\Promise\Timer\reject(2.0)->then(null, function (React\Promise\Timer\TimeoutException $e) { * echo 'Rejected after ' . $e->getTimeout() . ' seconds ' . PHP_EOL; * }); * ``` * * Internally, the given `$time` value will be used to start a timer that will * reject the promise once it triggers. This implies that if you pass a really * small (or negative) value, it will still start a timer and will thus trigger * at the earliest possible time in the future. * * This function takes an optional `LoopInterface|null $loop` parameter that can be used to * pass the event loop instance to use. You can use a `null` value here in order to * use the [default loop](https://github.com/reactphp/event-loop#loop). This value * SHOULD NOT be given unless you're sure you want to explicitly use a given event * loop instance. * * The returned promise is implemented in such a way that it can be cancelled * when it is still pending. Cancelling a pending promise will reject its value * with a `RuntimeException` and clean up any pending timers. * * ```php * $timer = React\Promise\Timer\reject(2.0); * * $timer->cancel(); * ``` * * @param float $time * @param LoopInterface $loop * @return PromiseInterface * @deprecated 1.8.0 See `sleep()` instead * @see sleep() */ function reject($time, LoopInterface $loop = null) { return sleep($time, $loop)->then(function () use ($time) { throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds'); }); }