Как использовать Phoenix.Channel.reply/2 для асинхронного ответа на нажатие канала

Я пытаюсь расширить пример Phoenix.Channel.reply/2 из документации Phoenix до полностью рабочего примера асинхронного ответа на push-события канала/сокета Phoenix:

Взято с сайта https://hexdocs.pm/phoenix/Phoenix.Channel.html#reply /2:

def handle_in("work", payload, socket) do
  Worker.perform(payload, socket_ref(socket))
  {:noreply, socket}
end

def handle_info({:work_complete, result, ref}, socket) do
  reply ref, {:ok, result}
  {:noreply, socket}
end

Я переработал пример следующим образом:

room_channels.ex

...
def handle_in("work", job, socket) do
  send worker_pid, {self, job}
  {:noreply, socket}
end

def handle_info({:work_complete, result}, socket) do
  broadcast socket, "work_complete", %{result: result}
  {:noreply, socket}
end
...

рабочий.ex

...
receive do
  {pid, job} ->
    result = perform(job) # stub
    send pid, {:work_complete, result}
end
...

Это решение работает, но оно не основано на создании и передаче socket_ref с socket_ref(socket) и на Phoenix.Channel.reply/2. Вместо этого он использует Phoenix.Channel.broadcast/3.

В документации подразумевается, что reply/2 специально используется для этого сценария асинхронного ответа на события push сокета:

ответить(arg1, arg2)

Асинхронно отвечает на нажатие сокета.

Полезно, когда вам нужно ответить на push-уведомление, которое иначе нельзя обработать, используя возврат {:reply, {status, payload}, socket} из ваших обратных вызовов handle_in. answer/3 будет использоваться в тех редких случаях, когда вам нужно выполнить работу в другом процессе и ответить, когда закончите, сгенерировав ссылку на отправку с помощью socket_ref/1.

Когда я генерирую и передаю socket_ref и полагаюсь на Phoenix.Channel.reply/2 для асинхронных ответов на push сокета, я вообще не заставляю его работать:

room_channels.ex

...
def handle_in("work", job, socket) do
  send worker_pid, {self, job, socket_ref(socket)}
  {:noreply, socket}
end

def handle_info({:work_complete, result, ref}, socket) do
  reply ref, {:ok, result}
  {:noreply, socket}
end
...

рабочий.ex

...
receive do
  {pid, job, ref} ->
    result = perform(job) # stub
    send pid, {:work_complete, result, ref}
end
...

Моя функция room_channels.ex handle_info вызывается, но reply/2, похоже, не отправляет сообщение по сокету. Я не вижу ни трассировки стека на stderr, ни вывода на stdout, указывающего на ошибку. Более того, отслеживание socket_ref просто увеличивает нагрузку на мой код.

В чем преимущество использования socket_ref и reply/2 по сравнению с моим решением с broadcast/3 и как я могу получить решение с reply/2 > работать?


person Marcel    schedule 23.05.2016    source источник


Ответы (2)


Я ошибся, пример с Phoenix.Channel.reply/2 будет работать:

room_channels.ex

...
def handle_in("work", job, socket) do
  send worker_pid, {self, job, socket_ref(socket)}
  {:noreply, socket}
end

def handle_info({:work_complete, result, ref}, socket) do
  reply ref, {:ok, result}
  {:noreply, socket}
end
...

рабочий.ex

...
receive do
  {pid, job, ref} ->
    result = perform(job) # stub
    send pid, {:work_complete, result, ref}
end
...

В моей реализации я сделал ошибку, отправив синхронный ответ на событие push с возвращаемым значением {:reply, :ok, socket} вместо {:noreply, socket}.

При более внимательном изучении кадров веб-сокета, отправляемых с сервера клиенту, я обнаружил, что браузер действительно получал ответы моего сервера от reply ref, {:ok, result}, но связанный обратный вызов никогда не вызывался.

Похоже, что клиентская библиотека Phoenix Socket.js принимает не более одного ответа на push-событие.

person Marcel    schedule 24.05.2016

надеюсь, я не опоздал к этому разговору.

Мне удалось использовать ваш код выше и заставить обратный вызов работать в javascript.

Хитрость заключается в прослушивании события phx_reply. Внутри priv/static/app.js каждый объект Channel javascript имеет список предопределенных в CHANNEL_EVENTS (строке) и настроен на его прослушивание в следующем блоке кода:

this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
  _this2.trigger(_this2.replyEventName(ref), payload);
});

То, что я сделал, было в обратном вызове канала on, я прослушиваю событие phx_reply:

channel.on("phx_reply", (data) => {
  console.log("DATA ", data);
  // Process the data
}

Это было протестировано на Elixir 1.3 и Phoenix 1.2.

Надеюсь, это поможет!

person chee yeo    schedule 25.02.2017