如何在灵药中实现延迟模式?

时间:2017-01-09 19:06:26

标签: elixir deferred

我怎样才能在elixir中实现延迟模式?

让我解释一下它是什么。假设我有一些fn(),应该在此之后延迟n秒后执行。但是如果我第二次调用fn()这个函数应该在第二次调用后的n秒内实现,依此类推。应该有一种方法来退出这个功能评估。

您可以查看Lodash的_.debounce函数作为参考。

3 个答案:

答案 0 :(得分:2)

一个非常天真和简单的解决方案可以使用原始流程。

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

这仍有一些可能的极端情况 - 生产版本可能会使用Task或GenServer。

答案 1 :(得分:0)

为了存储状态,您需要一个进程;一个简单的功能还不够。为此创建流程只需几行代码:

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

答案 2 :(得分:-1)

好的,这是一个简化的案例,让你前进: 此处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