Я разрабатываю API в эликсире, и у меня есть процесс, выполняющий 6 действий подряд. Мой вопрос: как я могу прервать это выполнение в случае сбоя любого из этих 6 действий?
Я не хотел бы каскадировать в несколько блоков case.
На самом деле я хотел бы что-то вроде "возврата".
У кого-нибудь есть хорошее решение для этого?
Когда у вас есть ряд последовательных условных выражений в Elixir (например, несколько блоков case), предложение with
(оператор? оператор?) может быть полезным.
Пример есть в документации Elixir.
Сравните вонючий беспорядок вложенных операторов case, например:
case op1(x) do
{:ok, result1} ->
case op2(result1) do
{:ok, result2} ->
case op3(result2) do
{:ok, result3} -> {:ok, result3}
{:error, error} -> {:error, error}
end
{:error, error} -> {:error, error}
end
{:error, error} -> {:error, error}
end
Это действительно сложно понять, и биты {:error, error} -> {:error, error}
кажутся излишними. По сути, мы просто хотим продолжать выполнять последовательные операции (1, 2, 3,...) или выйти с ошибкой.
Мы можем переписать приведенное выше, используя синтаксис with
— обратите внимание на направление стрелок!
with {:ok, result1} <- op1(x),
{:ok, result2} <- op2(result1) do
op3(result3)
else
{:error, error} -> {:error, error}
end
или, короче, опустите блок else
, если не требуется специальной обработки ошибок:
with {:ok, result1} <- op1(x),
{:ok, result2} <- op2(result1) do
op3(result3)
end
Вы используете запятую, чтобы отделить одну операцию от другой, наконец, завершаясь блоком do
, где вы можете выполнить последнюю операцию.
Если какая-либо из операций завершается сбоем, выполнение может быть перенаправлено для сопоставления в необязательном блоке else
— это наиболее полезно, если вам нужно различать разные возвращаемые значения (например, если требуется конкретное ведение журнала), в противном случае блок else
является избыточным и часто можно опустить.
Понимание блока with
— большой шаг в написании более чистого кода Elixir. Альтернативой использованию синтаксиса with
может быть написание нескольких сигнатур функций для обработки вывода каждого шага, но это похоже на много работы, которая не всегда оправдана.
Блок else
в форме {:error, error} -> {:error, error}
полностью лишний. {:error, error}
будет возвращен из with/1
без него, как только встретится.
Вы правы, конечно, я обновил объяснение. Однако я считаю полезным иметь блок else
в качестве примера в случае, если вам нужно сопоставлять разные возвращаемые значения.
Отсутствие возможности возврата какое-то время не давало мне покоя в эликсире. Я рекомендую вам использовать сопоставление с образцом. Я знаю, что это кажется неправильным, но в конце концов вы поймете простоту, стоящую за этим. Вы можете сделать это без вложения, просто создайте новую функцию для каждого ожидаемого шаблона.
Вот фрагмент, в котором я в основном повторяю self()
...
def handle_cast({:get_name, port, container}, state) do
app_name = Map.get(container, "Labels") |> Map.get("app")
GenServer.cast(self(), {:domain_map, app_name, port, container})
{:noreply, state}
end
def handle_cast({:containers, containers}, state) do
Enum.map(containers, fn container ->
GenServer.cast(self(), {:container, container})
end)
{:noreply, state}
end
def handle_cast({:container, container}, state) do
Enum.map(Map.get(container, "Ports"), fn port ->
GenServer.cast(self(), {:get_name, port, container})
end)
{:noreply, state}
end
...
Если шаблон не соответствует, это в основном ваш оператор возврата.
Можете ли вы показать этот код или его репрезентативный эквивалент?