Skip to content

Instantly share code, notes, and snippets.

@loilo
Last active November 21, 2022 08:24
Show Gist options
  • Select an option

  • Save loilo/d3865a595e54f1b76f4eda0abca43ea7 to your computer and use it in GitHub Desktop.

Select an option

Save loilo/d3865a595e54f1b76f4eda0abca43ea7 to your computer and use it in GitHub Desktop.
ReactPHP debounce

ReactPHP Debouncer

A simple & classic debounce function. You provide a callable and specify a $wait timer and get a callable (closure) back.

The closure takes the same arguments as the provided callable, but will only be executed $wait seconds after being invoked. Invoking the closure again before $wait seconds have passed will refresh the execution delay to the full $wait seconds again.

This can be useful for example for detecting if a socket hasn't received data in, let's say, 10 seconds:

$loop = React\EventLoop\Factory::create();


$receivedData = Debouncer::debounce($loop, 10, function () {
  echo "You have not received data in a while! Check your connection please.";
});

// Delay the connection warning on any data received
$someConnection->on('data', $receivedData);

// Make sure the delay timer is set up initially
$receivedData();


$loop->run();

API

Let's stick with the $receivedData function from above.

Invoke

To invoke the function (and thus refresh the delay timer), you may call the ->invoke() method:

$receivedData->invoke();

// Thanks to the __invoke() magic method, this does the same:

$receivedData();

Check for Pending Executions

Calling isPending() will tell if the function was invoked but has not been executed yet:

$receivedData->isPending(); // false

$receivedData->invoke();

$receivedData->isPending(); // true, will be executed in 10 seconds

Cancel Pending Executions

Sometimes you want a debounced function to stop it's job and also make sure no delayed action will be executed (think canceling a Promise). This can be done as follows:

$receivedData->invoke();

$receivedData->cancelPending();

// Delayed action will never be executed
<?php
use React\EventLoop\LoopInterface;
/**
* Create a debounced version of a provided callable.
*
* Debouncer::debounce($eventLoop, 5, function () {
* echo 'Delayed and debounced for 5 seconds.';
* });
*/
class Debouncer
{
protected $timer = null;
protected $loop;
protected $wait;
protected $callback;
/**
* Debounces a $callable by $wait seconds
*/
public static function debounce(LoopInterface $loop, float $wait, callable $callback)
{
return new static($loop, $wait, $callback);
}
public function __construct(LoopInterface $loop, float $wait, callable $callback)
{
$this->loop = $loop;
$this->wait = $wait;
$this->callback = $callback;
}
/**
* Invoke the debounced function via $debounced()
*/
public function __invoke(...$args)
{
$this->invoke($args);
}
/**
* Invoke the debounced function via $debounced->invoke()
*/
public function invoke(...$args)
{
$this->cancelPending();
$this->timer = $this->loop->addTimer($this->wait, function () use ($args) {
$this->timer = null;
call_user_func_array($this->callback, $args);
});
}
/**
* If there's a delayed function invokation pending
*/
public function isPending()
{
return !is_null($this->timer);
}
/**
* Cancels a possibly existing pending invokation
*/
public function cancelPending()
{
if (!is_null($this->timer)) {
$this->loop->cancelTimer($this->timer);
}
$this->timer = null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment