| 
<?php
 namespace React\Promise\Timer;
 
 use React\EventLoop\LoopInterface;
 use React\Promise\CancellablePromiseInterface;
 use React\Promise\PromiseInterface;
 use React\Promise\Promise;
 
 function timeout(PromiseInterface $promise, $time, LoopInterface $loop)
 {
 // cancelling this promise will only try to cancel the input promise,
 // thus leaving responsibility to the input promise.
 $canceller = null;
 if ($promise instanceof CancellablePromiseInterface || (!\interface_exists('React\Promise\CancellablePromiseInterface') && \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;
 };
 }
 
 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 ($promise instanceof CancellablePromiseInterface || (!\interface_exists('React\Promise\CancellablePromiseInterface') && \method_exists($promise, 'cancel'))) {
 $promise->cancel();
 }
 $promise = null;
 });
 }, $canceller);
 }
 
 function resolve($time, LoopInterface $loop)
 {
 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 ($time, $resolve) {
 $resolve($time);
 });
 }, 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');
 });
 }
 
 function reject($time, LoopInterface $loop)
 {
 return resolve($time, $loop)->then(function ($time) {
 throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds');
 });
 }
 
 |