1<?php 2 3namespace React\Promise\Timer; 4 5use React\EventLoop\LoopInterface; 6use React\Promise\CancellablePromiseInterface; 7use React\Promise\PromiseInterface; 8use React\Promise\Promise; 9 10function timeout(PromiseInterface $promise, $time, LoopInterface $loop) 11{ 12 // cancelling this promise will only try to cancel the input promise, 13 // thus leaving responsibility to the input promise. 14 $canceller = null; 15 if ($promise instanceof CancellablePromiseInterface || (!\interface_exists('React\Promise\CancellablePromiseInterface') && \method_exists($promise, 'cancel'))) { 16 // pass promise by reference to clean reference after cancellation handler 17 // has been invoked once in order to avoid garbage references in call stack. 18 $canceller = function () use (&$promise) { 19 $promise->cancel(); 20 $promise = null; 21 }; 22 } 23 24 return new Promise(function ($resolve, $reject) use ($loop, $time, $promise) { 25 $timer = null; 26 $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) { 27 if ($timer) { 28 $loop->cancelTimer($timer); 29 } 30 $timer = false; 31 $resolve($v); 32 }, function ($v) use (&$timer, $loop, $reject) { 33 if ($timer) { 34 $loop->cancelTimer($timer); 35 } 36 $timer = false; 37 $reject($v); 38 }); 39 40 // promise already resolved => no need to start timer 41 if ($timer === false) { 42 return; 43 } 44 45 // start timeout timer which will cancel the input promise 46 $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject) { 47 $reject(new TimeoutException($time, 'Timed out after ' . $time . ' seconds')); 48 49 // try to invoke cancellation handler of input promise and then clean 50 // reference in order to avoid garbage references in call stack. 51 if ($promise instanceof CancellablePromiseInterface || (!\interface_exists('React\Promise\CancellablePromiseInterface') && \method_exists($promise, 'cancel'))) { 52 $promise->cancel(); 53 } 54 $promise = null; 55 }); 56 }, $canceller); 57} 58 59function resolve($time, LoopInterface $loop) 60{ 61 return new Promise(function ($resolve) use ($loop, $time, &$timer) { 62 // resolve the promise when the timer fires in $time seconds 63 $timer = $loop->addTimer($time, function () use ($time, $resolve) { 64 $resolve($time); 65 }); 66 }, function () use (&$timer, $loop) { 67 // cancelling this promise will cancel the timer, clean the reference 68 // in order to avoid garbage references in call stack and then reject. 69 $loop->cancelTimer($timer); 70 $timer = null; 71 72 throw new \RuntimeException('Timer cancelled'); 73 }); 74} 75 76function reject($time, LoopInterface $loop) 77{ 78 return resolve($time, $loop)->then(function ($time) { 79 throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds'); 80 }); 81} 82