如何在长生不老药中实现延迟模式?
让我解释一下它是什么。假设我有一些fn()
应该在现在之后延迟 n
秒来实现。但是,如果我第二次调用此函数fn()
则应在第二次调用后的 n
秒内实现,依此类推。也应该有一种方法可以退出此函数计算。
你可以看看 Lodash 的 _.debounce 函数作为参考。
一个非常幼稚和简单的解决方案可以使用原始进程。
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
然而,这有很多问题——我们的"去抖动"过程实际上永远存在,我们无法取消去抖动,可靠性充其量是粗略的。我们可以改进,但我们会失去我们可以调用的简单乐趣的返回值,相反,我们需要调用一个特殊函数来"应用"我们的去抖动器。
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
这仍然有一些可能的极端情况 - 生产版本可能会使用任务或GenServer。
为了存储状态,你需要一个进程;一个简单的函数是不够的。为此创建一个过程只需几行代码:
defmodule Debounce do
def start_link(f, timeout) do
spawn_link(__MODULE__, :loop, [f, timeout])
end
def loop(f, timeout) do
receive do
:bounce -> loop(f, timeout)
:exit -> :ok
after timeout ->
f.()
end
end
end
您可以将此进程发送到:bounce
,它会将其超时重置为 Debounce.start_link/2
中指定的超时。您也可以将此过程发送到:exit
,它将自行退出而不运行该函数。
测试:
f = Debounce.start_link(fn -> IO.inspect(:executing) end, 1000)
IO.puts 1
send f, :bounce
:timer.sleep(500)
IO.puts 2
send f, :bounce
:timer.sleep(500)
IO.puts 3
send f, :bounce
:timer.sleep(500)
IO.puts 4
send f, :bounce
:timer.sleep(2000)
IO.puts 5
输出:
1
2
3
4
:executing
5
好的,这里有一个简化的案例来帮助你:在这里n
不是以秒为单位,而是循环步骤,因此您将需要大n
才能看到任何延迟。在这里,我使用 IO.puts
作为调用函数的示例。
defmodule myModule do
def loop(list,count) do
receive do
n -> list = list ++ n
:die ->
Process.exit(self(), :kill )
end
if count == 0 do
IO.puts( "timeout" )
[head|tail] = list
loop(tail, head)
else
loop(list, count-1)
end
end
end