一个非常天真和简单的解决方案可以使用原始流程。
defmodule Debounce do
def start(fun, timeout) do
ref = make_ref()
# this function is invoked when we wait for a next application
recur = fn recur, run ->
receive do
^ref ->
# let's start counting!
run.(recur, run)
end
end
# this function is invoked when we "swallow" next applications
# and wait until we finally apply the function
run = fn recur, run ->
receive do
^ref ->
# let's reset the counter
run.(recur, run)
after
timeout ->
# time is up, let's call it for real & return to waiting
fun.()
recur.(recur, run)
end
end
pid = spawn_link(fn -> recur.(recur, run) end)
fn -> send(pid, ref) end
end
end
我们来看一个例子
iex> f = Debounce.start(fn -> IO.puts("Hello"), 5000)
iex> f.()
iex> f.()
# wait some time
Hello
iex> f.() # wait some time
Hello
但是,这有很多问题 - 我们的“去抖动”过程实际上永远存在,我们无法取消去抖动,并且可靠性充其量是粗略的。我们可以改进,但我们会失去我们可以调用的简单 fun 的返回值,而是需要调用一个特殊函数来“应用”我们的去抖动器。
defmodule Debounce do
def start(fun, timeout) do
ref = make_ref()
# this function is invoked when we wait for a next application
recur = fn recur, run ->
receive do
{^ref, :run} ->
# let's start counting!
run.(recur, run)
{^ref, :cancel} ->
:cancelled
end
end
# this function is invoked when we "swallow" next applications
# and wait until we finally apply the function
run = fn recur, run ->
receive do
{^ref, :run} ->
# let's reset the counter
run.(recur, run)
{^ref, :cancel} ->
:cancelled
after
timeout ->
# time is up, let's call it for real & return to waiting
fun.()
recur.(recur, run)
end
end
pid = spawn_link(fn -> recur.(recur, run) end)
{pid, ref}
end
def apply({pid, ref}) do
send(pid, {ref, :run})
end
def cancel({pid, ref}) do
send(pid, {ref, :cancel})
end
end
我们来看一个例子:
iex> deb = Debounce.start(fn -> IO.puts("Hello"), 5000)
iex> Debounce.apply(deb)
iex> Debounce.apply(deb)
# wait some time
Hello
iex> Debounce.apply(deb)
iex> Debounce.cancel(deb)
# wait some time
# nothing
这仍然有一些可能的极端情况 - 生产版本可能会使用 Task 或 GenServer。