<?php namespace React\Promise\Timer; use React\Promise\CancellablePromiseInterface; use React\EventLoop\LoopInterface; 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) { // 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) { $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'); }); }