Simple Javascript like timer implementation for PHP.
README.md: Refined about timing. | e84c103, 4 Aug 2018 |
---|---|
src/ | |
composer.json | |
README.md | |
test.php |
PHP Timer
This is simple Javascript like timer implementation for PHP. It's not meant to be a production library so don't use it.
Functions:
setTimeout(callable $callback, int $milliseconds): int
- schedules a timer and returns its IDsetInterval(callable $callback, int $milliseconds): int
- schedules a periodic timer and returns its IDclearTimeout(int $id): void
- aborts a timerclearInterval(int $id): void
- aborts an interval
Original solution by Jakub Vrána found years ago here; this one is different, since I don't think the proposed code works the same way the Javascript does.
There is an important thing you might not notice in Javascript. Whenever you set a timer, remaining time is not being updated while a synchronous, blocking code is running. Since almost all the API Javascript has is asynchronous, it's not an issue.
Take a look at this code:
setTimeout(function() {
print 'Hello ';
}, 0);
sleep(1);
print 'World!';
At first look, it seems like Hello
should print first (time set to 0
ms) and World!
should follow a second later. Instead, this is what actually happens:
- our
Hello
callback gets queued with time set to0
ms, sleep(1)
hangs the execution for a second - that's because it's synchronous and blocking,World!
gets printed, because everything behind the initialsetTimeout
is still a synchronous code,- now, our internal event loop takes the timer with least remaining time (waits eventually) and executes it.
This is in fact exactly the same thing Javascript does. It doesn't have a sleep()
function, but here is how you could emulate it:
setTimeout(() => console.log('Hello '), 0);
alert('Read this for a second and then close it.');
console.log('World!');
You can run this code in your browser and see what happens - just like in PHP, World!
will be printed first and Hello
will follow right after closing the alert window.
Solution
The thing we can realize here is that Javascript event loop doesn't have any sort of interruption nor threading and it doesn't measure remaining time by abusing OS current time. Instead, it sleeps till a timer is ready when there's no synchronous code being executed. Synchronous code execution simply delays all pending timers.
Therefore, this is why I think my implementation is as close to Javascript behavior as it can be.
As shown above, the correct (well, Javascript) solution doesn't use microtime()
, ticks
nor threads. Whenever a timer is set, we'll store the user defined time (*1000
- we'll need microseconds instead of milliseconds for usleep()
later) and set its remaining time the same.
Now, we'll run our event loop on shutdown using register_shutdown_function()
and till a timer exists, we will repeat this:
- obtain a timer with least remaining time,
usleep()
on remaining time,- decrease other timers remaining time by the remaining time of currently executed timer,
- execute the current timer (reset its remaining time if it's periodic, drop otherwise).
Note that since we're calling usleep()
here, timing accuracy suffers. That's actually another con for microtime()
- if we would decrease remaining time by something measured using this function, timers with low fractions of seconds might be executed in an unexpected order, which is a serious issue we're trying to mitigate by maintaining the remaining time manually, using exactly calculated microseconds. This applies for Javascript as well - timers are guaranteed to be executed in an expected order, not in an accurate expected time.