Process.send_after/4
とGenServer.handle_info/2
を利用すれば定期的な処理が実行できることを先日知りました。
前回は素のGenServerで実施してみたので、今回はPhoenix LiveViewで実施してみました。
最初に結論
Phoenix LiveViewでも問題なく実行できました。
画面中では以下の2つの定期更新が行われています。
* 1秒間隔で更新される時刻
* 3秒ごとに更新されるFizzBuzzの値
実装
以下の環境で動作確認しました。
- Elixir 1.9.4-otp-22
- Phoenix Framework : 1.4.14
- Phoexix LiveView : 0.7.1
Phoenix LiveViewはバージョンによる差異が割と出てくるので、Phoenix LiveView側の設定周りなどの説明は省略。
ただし、試した内容自体はLiveViewのバージョンにはほとんど依存しないので、0.7.1よりも古いバージョンでも動くはず。
コード
実装コードのfizzbuzz.ex
と画面テンプレートのautofizzbuzz.html.leex
を載せます。
自分は好みでrenderの中身は外部ファイルにしていますが、今回のレベルでなら~L
を利用してfizzbuzz.ex
内に記述した方が簡潔ではありますね。
defmodule HelloWeb.Fizzbuzz do
use Phoenix.LiveView
import Calendar.Strftime
def render( assigns ) do
HelloWeb.LiveView.render("autofizzbuzz.html",assigns)
end
## 補足: LiveViewが0.5.2より以前の場合は、mount/2になります。
## _paramsをなくして、mount(_session, socket )になります。
def mount(_params, _session, socket ) do
if connected?(socket) do
Process.send_after(self(), :tick, 1000)
Process.send_after(self(), :fizzbuzz, 3000)
end
{ :ok, assign( socket,
time: local_time(),
num: 0,
val: "initial" ) }
end
def handle_info(:tick, socket) do
Process.send_after(self(), :tick, 1000)
{:noreply, assign(socket, time: local_time())}
end
def handle_info(:fizzbuzz, socket) do
Process.send_after(self(), :fizzbuzz, 3000)
next_num = socket.assigns.num + 1
{num, val} = fizzbuzz(next_num)
{:noreply, assign( socket, num: num, val: val ) }
end
##### local
defp local_time() do
:calendar.local_time() |> strftime!("%r")
end
defp calc_fb({n, 0, 0}), do: {n, "FizzBuzz"}
defp calc_fb({n, 0, _}), do: {n, "Fizz"}
defp calc_fb({n, _, 0}), do: {n, "Buzz"}
defp calc_fb({n, _, _}), do: {n, to_string(n)}
defp fizzbuzz(n) do
cond do
n < 1 -> {n , "Cannot calculate"}
true -> calc_fb({n, rem(n,3), rem(n, 5)})
end
end
end
<h2>ここに現在時刻が表示されます。</h2>
<span><%= @time %></span>
<h2>ここにFizzBuzzの値が表示されます。</h2>
<span><%= @num %>:<%= @val %></span>
解説
ポイントだけ。
- 初期画面表示時に呼ばれる
mount/3
中で、2つのProcess.send_after
をcallしています。- メッセージ「
:tick
」を1秒(1000ms)間隔で。 - メッセージ「
:fizzbuzz
」を3秒(3000ms)間隔で。 - なお、どちらもコネクションが確立されてる場合のみ実行。
- メッセージ「
- メッセージ「
:tick
」のなかで、再びメッセージ「:tick
」のhandle_info/2
をcall。 - メッセージ「
:fizzbuzz
」のなかで、再びメッセージ「:fizzbuzz
」のhandle_info/2
をcall。 - FizzBuzzに使う数値は
socket
中から取得する。-
socket.assigns.<項目名>
で取得します。 - コード中にも記載していますが、
@num
を取得する場合は、socket.assigns.num
と指定します。
-
それと、コードのコメントにも書いていますが、LiveViewのコード中で必須のmount関数
が、v0.5.2まではmount/2
だったのに対して、v0.6.0以降はmount/3
となっています。
mount/3
では第一引数にparams
を用意してやる必要がありますが、今回の例でparams
は利用していないのでバージョンの違いによる差異は感じません。
まとめ
GenServerがベースになっているので当たり前といえば当たり前なんですが、Phoenix LiveViewと「Process.send_after/4
+handle_info/2
の組み合わせ」の相性は良さげです。
参考情報
- https://hexdocs.pm/elixir/GenServer.html#module-receiving-regular-messages
- https://hexdocs.pm/elixir/Process.html#send_after/4
- https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript
- https://hexdocs.pm/phoenix_live_view/0.7.1/Phoenix.LiveView.html
- https://qiita.com/MzRyuKa/items/268bc4824703feaf8b2c