如何在长生不老药中实现延迟模式



如何在长生不老药中实现延迟模式?

让我解释一下它是什么。假设我有一些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

最新更新