继续探索with语句
在上一篇博客《漂亮的with,鱼与熊掌可以兼得》中,展现了with的优雅之处,然而在比较with
与|>
时,言犹未尽,讲得不够透彻。
在那篇博客中,我说:
毕竟with/1并不是try/catch,它并不能捕获执行中抛出的错误,然后转向else进行错误处理。只有当模式匹配出现错误时,才会转向else。 要优雅地处理错误,并用优雅的with/1将逻辑串联起来,就需要重构get_user,get_response,send_response等函数。当程序逻辑正确时,返回一个tuple对象{:ok, result};如果出现错误,则返回{:error, error}。
如果进行了这样的重构,是否意味着|>
也可以将健壮性与优雅结合起来呢?因为在Elixir中,函数的定义使用了模式匹配,因此,在定义参与|>
操作的函数时,可以通过模式匹配来考虑各种情况,这其中可以包含对{:error, error}
情形的处理,使得数据流不至于在流经该函数时因为错误而崩溃掉。
Joseph Kain在博客Learning Elixir's with给出了一个例子,执行了ecto查询:
defp results(conn, search_params) do
conn.assigns.current_user
|> Role.scope(can_view: Service)
|> within(search_params)
|> all
|> preload(:user)
end
defp within(query, %{"distance" => ""}), do: {:ok, query}
defp within(query, %{"distance" => x, "location" => l} do
{dist, _} = Float.parse(x)
Service.within(query, dist, :miles, l)
end
defp within(query, _), do: {:ok, query}
defp all({:error, _} = result), do: result
defp all({:ok, query}), do: {:ok, Repo.all(query)}
defp preload({:error, _} = result), do: result
defp preload({:ok, enum}, field) do
{:ok, Repo.preload(enum, field)}
end
且不管业务,我们可以清晰地看到在all
与preload
函数增加了对{:error, _}
分支的处理,这样就可以避免数据流动的管道不至于因为错误而终止。
如果使用with
,虽然结构不如|>
清晰直观,却可以避免在all
与preload
中去处理错误分支。因为with
语句同样使用了模式匹配,只要参与的方法不能满足模式匹配的条件,就不会再执行do
,从而规避了错误引起的终止:
defp results(conn, search_params) do
with user <- conn.assigns.current_user,
query <- Role.scope(user, can_view: Service),
{:ok, query} <- within(query, search_params),
query <- all(query),
do: {:ok, preload(query, :user)}
end
defp within(query, %{"distance" => ""}), do: {:ok, query}
defp within(query, %{"distance" => x, "location" => l} do
{dist, _} = Float.parse(x)
Service.within(query, dist, :miles, l)
end defp within(query, _), do: {:ok, query}
defp all(query), do: Repo.all(query)
defp preload(enum, field) do: {:ok, Repo.preload(enum, field)}
由于all/1
与preload/2
仅仅是对Repo.all/1
与Repo.preload/2
的简单封装,所以可以进一步简化代码:
defp results(conn, search_params) do
with user <- conn.assigns.current_user,
query <- Role.scope(user, can_view: Service),
{:ok, query} <- within(query, search_params),
query <- Repo.all(query),
do: {:ok, Repo.preload(query, :user)}
end
多余的代码被有效地清除了,而功能与健壮性并没有得到任何降低。这是within的奇妙之处。
- Hbase 源码分析之 Get 流程及rpc原理
- MOCTF WEB 题解
- HBase行锁与MVCC分析
- HBase行锁探索
- Spring Cloud构建微服务架构:分布式服务跟踪(抽样收集)【Dalston版】
- HBase client访问ZooKeeper获取root-region-server DeadLock问题(zookeeper.ClientCnxn Unable to get data of zn
- zookeeper学习系列:四、Paxos算法和zookeeper的关系
- 有了phonegap你还android吗?
- zookeeper学习系列:三、利用zookeeper做选举和锁
- Spring Cloud构建微服务架构:分布式服务跟踪(收集原理)【Dalston版】
- zookeeper学习系列:二、api实践
- Spring Cloud构建微服务架构:分布式服务跟踪(整合logstash)【Dalston版】
- Spring Cloud构建微服务架构:分布式服务跟踪(整合zipkin)【Dalston版】
- 困扰我多年的Connection reset问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 聊聊dubbo-go的GenericFilter
- Salesforce LWC学习(十九) 针对 lightning-input-field的label值重写
- MySQL定时备份方案
- Nginx多方面调优策略
- 太厉害了!这应该是目前Redis可视化工具最全的横向评测
- pip install时timeout设置
- 聊聊dubbo-go的TpsLimitFilter
- 聊聊dubbo-go的TokenFilter
- 你这磨人的小妖精——选中文本并标注的实现过程
- VUE跨页面传值的精妙
- 一文解决如何提取TCGA配对表达矩阵
- CSS中的伪类选择器、颜色、度量单位、文本字体及文本样式设置
- CSS盒模型、边框和背景、表格和列表、颜色和透明度、阴影和轮廓及长度单位rem
- C++核心准则CP.110:不要自已为初始化编写双重检查锁定代码
- BOSS直聘招聘信息获取之爬虫程序数据处理