return两个返回值_异步函数的两个视角

我们来一起看一下两个程序员之间的故事。

以下示例代码是用Scala写的,不过本文所讲的话题并不仅限于Scala,任何有Future/Promise支持的语言都是适用的。

下面这个wiki页面罗列了各个有Future/Promise支持的语言,已经涵盖了大多数的常用语言。

Future与promise实现列表

我是异步函数的编写者

我写了两个异步函数,来提供给其他程序员同事使用。

type CallBack = Try[String] => Unitdef pretendCallAPI(callBack: CallBack, okMsg: String, failedMsg: String) = {val task = new TimerTask {override def run() = {val percentage = Random.between(1, 100)if (percentage >= 50)callBack(Success(okMsg))else if (percentage <= 30)callBack(Failure(new Exception(failedMsg)))elsecallBack(Failure(new Exception("network problem")))}}new Timer().schedule(task, Random.between(200, 500))
}val searchTB = pretendCallAPI(_, "product price found", "product not listed")
val buyFromTB = pretendCallAPI(_, "product bought", "can not buy, no money left")

这两个异步函数: searchTB用来从淘宝搜索物品,另一个buyFromTB用来购买搜到的物品。

由于仅仅是为了演示而写的,他们两个都是基于一个叫做pretendCallAPI的函数实现的。

顾名思义,pretendCallAPI并不会真的去调用淘宝的API,而只是模拟API的行为。

这个pretendCallAPI函数有几个行为特征:

  • 每次耗时200到500毫秒之间
  • 每次执行有50%的几率成功
  • 20%的几率遇到网络故障
  • 另外30%的几率虽然网络没问题但是服务器会给你一个非正常的结果

当然,由于我写的是异步算法,需要避免block caller thread。

所以当你调用pretendCallAPI的时候,这个函数是瞬间立即返回的。

那么当然我就无法在函数返回的时候return什么有用的东西给你了。

如果你想知道执行的结果到底是啥,你需要传给我一个CallBack,在我执行完后,通过CallBack来告知你执行的结果。

这个CallBack的完整签名表达式展开是Try[String] => Unit

大家看searchTB和buyFromTB可能觉得他们长的有点奇怪,这是Scala里柯里化的写法。

也就是通过把pretendCallAPI包一层来构造新的函数,锁死两个参数,剩下的一个参数(也就是CallBack)就变成了新构造出来的函数的唯一参数了。

也就是说searchTB和buyFromTB的签名是(Try[String] => Unit) => Unit。

关于柯里化这个语言特性的更多信息:

https://cuipengfei.me/blog/2013/12/25/desugar-scala-6/

好了,现在这两个函数可以提供给大家使用了。

我是异步函数的调用者

听说异步函数已经写好了,我终于可以用他们来实现剁手业务了。

听函数作者讲了一下,用起来应该不会很难,那我来实现一下吧。

def searchPriceThenBuy() = {searchTB {case Success(searchMsg) =>println(searchMsg)buyFromTB {case Success(buyMsg) => println(buyMsg)case Failure(err) => println(err.getMessage)}case Failure(err) => println(err.getMessage)}
}

使用searchTB和buyFromTB并不难. 他们两个都是接受CallBack作为参数的函数。

CallBack本身是个函数,它的签名是Try[String] => Unit。

而Try有两种形式,分别是Success和Failure。

所以在调用searchTB和buyFromTB的时候,必须把两个分支都给到(以免pattern match不到)。

这样在异步函数有结果的时候(无论成败)才能call back过来到我的代码,以便我能够在合适的时机做后续的处理(无论是基于成功做后续业务,还是做error handling)。

关于pattern match,可以参考这里:

https://cuipengfei.me/blog/2013/12/29/desugar-scala-8/

https://cuipengfei.me/blog/2015/06/16/visitor-pattern-pattern-match/

这段代码跑一下的话,会有这么几种结果:

  • 搜到了,也买到了
  • 搜到了,购买时遇到了网络故障
  • 搜到了,由于支付宝钱不够而没买到
  • 没搜到,购买行为未触发
  • 搜索遇到网络故障,购买行为未触发

一共就这么几种可能,因为pretendCallAPI是跑概率的,多跑几次这些情况都能遇到。

虽然实现出来不难,执行结果也没问题,但是总有点隐忧。

这里只有searchTB和buyFromTB两个函数,如果其他场景下我需要把更多的异步函数组合起来使用呢?岂不是要缩进很多层?

当然,缩进只是个视觉审美问题,是个表象,不是特别要紧.关键是我的业务逻辑很容易被这样的代码给割裂的鸡零狗碎,那就不好了。

我要给上游编写异步函数的同事反馈一下,看是否有办法解决这个问题。

镜头切回到异步函数编写者

之前写的两个函数反馈不太好,主要是因为同事们认为使用CallBack不是最优的方式。

这个反馈确实很中肯,如果只有一个异步函数单独使用,用CallBack也没什么太大的问题,如果是很多个异步函数组合使用确实会形成多层嵌套的问题。

我作为上游程序员,确实需要更多地为下游调用者考虑。

既然如此,那我改版一下,免除掉让下游使用CallBack的必要性。

type CallBackBasedFunction = (CallBack) => Unitdef futurize(f: CallBackBasedFunction) = () => {val promise = Promise[String]()f {case Success(msg) => promise.success(msg)case Failure(err) => promise.failure(err)}promise.future
}val searchTBFutureVersion = futurize(searchTB)
val buyFromTBFutureVersion = futurize(buyFromTB)

先定义一个CallBackBasedFunction,它代表一个接受CallBack为参数的函数的签名。

表达式展开后就是: (Try[String] => Unit) => Unit

这就符合了searchTB和buyFromTB两个函数的签名。

futurize算是个higher order function,它接受一个CallBackBasedFunction作为参数,返回一个() => Future[String]。

(Future是Scala标准库的内容,可以认为和JS Promises/A+是类似的概念)

也就是说futurize可以把searchTB和buyFromTB改造成返回Future的函数。上面代码最后两行就是改造的结果。

这样,原本接受CallBack做为参数且没有返回值的函数,就变成了不接受参数且返回Future的函数。

再看futurize的具体实现,它使用了Scala的Promise,让返回的Future在原版函数成功时成功,在原版函数失败时失败。

这样,我就得到了searchTBFutureVersion和buyFromTBFutureVersion这两个仍然是立即瞬间返回,不会block caller thread的函数

关于Scala中Promise和Future的更多信息:

https://docs.scala-lang.org/overviews/core/futures.html

镜头再切到异步函数调用者

现在有了searchTBFutureVersion和buyFromTBFutureVersion,我来试着重新实现一次:

def searchPriceThenBuyFutureVersion() = {val eventualResult = for {searchResult <- searchTBFutureVersion().map(msg => println(msg))buyResult <- buyFromTBFutureVersion().map(msg => println(msg))} yield (searchResult, buyResult)eventualResult.onComplete {case Failure(err) => println(err.getMessage)case _ =>}
}

这里用到了Scala的for comprehension,编译后会变成map,flatMap等等monadic operator。

而map,flatMap等操作符正是Scala中Future拿来做组合用的。

这样,用for把两个返回Future的异步函数组织起来,形成一个新的Future,然后在新的Future complete时统一处理异常。

关于for的更多信息:

https://cuipengfei.me/blog/2014/08/30/options-for/

这次实现的代码与上次的行为是一致的,没什么两样。

不过我的业务代码从鸡零狗碎变成了平铺直叙平易近人。

(这种效果在这里表现的并不是特别突出,不过很容易想象如果需要组合使用的异步函数更多一些的话,这种效果的好处就显露出来了)

当然了,让业务代码易读易懂主要还是要靠个人奋斗,而有了Promise和Future这种历史进程的推力,则更有增益作用。

小结

最近在看Scala Reactive的一些内容

想起了很久之前写过一篇叫做自己动手实现Promises/A+规范的博客,用JS实现了一个简版的Promise:

https://cuipengfei.me/blog/2016/05/15/promise/

我在当时的一段演示代码里面写了两句注释:

Promise的作用在于

  1. 给异步算法的编写者和使用者之间提供一种统一的交流手段
  2. 给异步算法的使用者提供一种组织代码的手段,以便于将一层又一层嵌套的业务主流程变成一次一次的对then的调用

不过当时的博客里只讲了实现Promise规范的事情,并没有详细解释过这两句话。

既然又遇到了这个话题,于是写点Scala来把当时没展开写到的内容补充了一下。

上文的四个镜头展现了两个角色的思考过程,通过这个过程其实也就解释了上面两句注释的含义。

1.给异步算法的编写者和使用者之间提供一种统一的交流手段

所谓统一的交流手段,其实就是异步函数的签名问题。

由于需要处理的业务五花八门,异步函数接受的参数列表没法统一,但是返回值是可以统一的。

一个异步函数,接受了外界给的参数,立即瞬间返回一个Js的Promise或者Scala的Future(或者是任何语言中类似概念的叫法)。

然后在异步任务执行完的时候把Promise resolve/reject掉(让Future success或者failure),借此来让调用方的代码知道该到了它跑后续处理的时候了。

这样我们就获得了一个sensible default,无需在每次设计异步函数的时候都去商议该返回什么东西,该怎么获得异步执行的结果。

2.给异步算法的使用者提供一种组织代码的手段,以便于将一层又一层嵌套的业务主流程变成一次一次的对then的调用
所谓组织代码的手段,就是关于异步函数调用者的那两个镜头的内容了。

一开始CallBack套着CallBack,异步的味道很重,这体现出了代码的组织方式在向代码的技术实现低头。或者说是代码的技术实现干扰了我行文的风格

后来变成了看起来很像是消费同步函数结果的写法。从而让我惯常的文风得以保持。


文/ThoughtWorks 崔鹏飞

异步函数的两个视角 - ThoughtWorks洞见​insights.thoughtworks.cn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/505242.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python怎么控制速度_如何控制python的ThreadPoolExecutor的吞吐量速度?

我使用python的concurrent.futuresThreadPoolExecutor启动异步任务。按照this方法&#xff0c;我使用tqdm进度条监视异步调用的进度。在我的代码如下所示&#xff1a;with concurrent.futures.ThreadPoolExecutor(max_workers n_jobs) as executor:future_to_url {executor.s…

mysql57win10安装配置_Win10 OS安装(配置)MySQL 5.7(解压版)

Win10 OS安装(配置)MySQL 5.7(解压版)下载及解压文件名&#xff1a;mysql-5.7.27-win32.zipzip是解压版&#xff0c;msi是安装版&#xff0c;本教程仅说明zip格式的配置方法。解压(假设解压后根路径为D:\ide\mysql-5.7.27-win32)相关截图添加环境系统变量path 增加D:\ide\mysql…

mysql数据库更新数据库语句_MySQL数据库之UPDATE更新语句精解

UPDATE和REPLACE基本类似&#xff0c;但是它们之间有两点不同。1. UPDATE在没有匹配记录时什么都不做&#xff0c;而REPLACE在有重复记录时更新&#xff0c;在没有重复记录时插入。2. UPDATE可以选择性地更新记录的一部分字段。而REPLACE在发现有重复记录时就将这条记录彻底删除…

ubuntu安装python编译器_Ubuntu中安装VIM编辑器

Ubuntu安装好以后&#xff0c;默认是安装使用nano编辑器。不过这对于用惯了vim的人可能会有些不习惯。好在Ubuntu下安装vim还是比较简单的&#xff0c;使用如下命令即可&#xff1a;sudoapt-get install vim(apt-get install vim-full这下就好了 在输入 :syntax on 或者把/etc/…

pep8 python 编码规范_如何用好python编码规范,写一手漂亮的代码

前一段时间在编写python 代码的时候编辑器中一直在提示规范问题&#xff0c;因为强迫症的原因&#xff0c;我决定遵循python 的编码规范去编码&#xff0c;然后把需要注意的点记录下来&#xff0c; 帮助自己和大家一起成长。这是我的main.py文件中的一部分代码&#xff0c;经过…

MySQL故障检测_检测MySQL的表的故障的方法

表的故障检测和修正的一般过程如下&#xff1a;检查出错的表。如果该表检查通过&#xff0c;则完成任务&#xff0c;否则必须修复出错的数据库表。在开始修复之前对表文件进行拷贝&#xff0c;以保证数据的安全。开始修复数据库表。如果修复失败&#xff0c;从数据库的备份或更…

mysql约束_Mysql约束条件

约束条件1约束条件约束是一种限制&#xff0c;通过对表中的数据做出限制&#xff0c;来确保表中数据的完整性&#xff0c;唯一性默认约束CREATE TABLE tb(id INT DEFAULT a ,name VARCHAR(20));插入数据的时候&#xff0c;如果没有明确为字段赋值&#xff0c;则自动赋予默认值在…

mysql导入greenplum_greenPlum中通过gpfdist导入文本数据到数据库表中

1.python版本要求2.4.4以上[roottest install]# pythonPython 2.6.2 (r262:71600, May 14 2009, 10:46:21)[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2Type "help", "copyright", "credits" or "license" for more informatio…

解决方案和项目的关系_项目经理入门知识系列之《项目团队的职责分工》

项目团队的组织结构组织结构项目经理职责整合制定项目计划所需的活动。整合执行项目计划所需的活动。整合进行范围变更所需的活动。1、目经理负责对横跨多个职能线的活动进行协调和整合。整合管理2、项目经理核心技能---沟通能力(因为他的权力太少了)如果一个人有良好的沟通与人…

python小车行驶路线图_基于opencv-Python小车循线学习笔记

基于opencv-Python小车循线学习笔记加入摄像头模块&#xff0c;让小车实现自动循迹行驶思路为&#xff1a;摄像头读取图像&#xff0c;进行二值化&#xff0c;将白色的赛道凸显出来选择下方的一行像素&#xff0c;黑色为0&#xff0c;白色为255找到白色值的中点目标中点与标准中…

python 字符串大小写转换 其它不变_python字符串大小写如何转换

平常开发过程中对字符串的一些操作&#xff1a;#字母大小写转换#首字母转大写#去除字符串中特殊字符(如&#xff1a;_&#xff0c;.&#xff0c;,&#xff0c;;)&#xff0c;然后再把去除后的字符串连接起来#去除hello_for_our_world中的_,并且把从第一个_以后的单词首字母大写…

java读取文件夹_Java读取某个文件夹下的所有文件(支持多级文件夹)

packagecom.vocy.water.batch;importjava.io.FileNotFoundException;importjava.io.IOException;importjava.io.File;public classCopyOfReadFile {publicCopyOfReadFile() {}/*** 读取某个文件夹下的所有文件(支持多级文件夹)*/public static boolean readfile(String filepat…

武汉大学信息管理学院java上机考试_java上机试题

展开全部import javax.swing.*;import java.awt.*;import java.awt.event.*;public class Main extends JFrame implements ActionListener{LabledText upperBase new LabledText("上底&#xff1a;"),62616964757a686964616fe59b9ee7ad9431333337373561lowerBase …

python中cmd全称_【转】Python中执行cmd的三种方式

原文链接&#xff1a;http://blog.csdn.net/menglei8625/article/details/7494094目前我使用到的python中执行cmd的方式有三种&#xff1a;1. 使用os.system("cmd")这是最简单的一种方法&#xff0c;特点是执行的时候程序会打出cmd在linux上执行的信息。使用前需要im…

数据库字段 到类 java bean_将数据库中表的字段自动转换为javaBean实体类

具体代码如下&#xff1a;package param;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.sql.Connection;import java.sql.DatabaseMetaData;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.Resul…

el-drawer点击的时候为什么有边框_剪映教学之视频拍摄加剪辑【一】:出视频上下黑色边框模糊效果,视频广告配音...

抖音小视频已经成为风靡全国的一个app了&#xff0c;很多人都喜欢看抖音来打发时间&#xff0c;而经常看小视频的应该都见到过这种现象&#xff0c;就是有一些小视频我们在观看的时候&#xff0c;发现这个小视频的上下都有黑色边框或者模糊的效果&#xff0c;实际这都是一些拍摄…

java swt 下拉列表_求助:SWT 下拉列表

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼public class HelloWorldCombo {//下拉列表项 private static final String[] ITEMS { "Alpha", "Bravo", "Charlie", "Delta","Echo", "Foxtrot", "Golf"…

python html转换为普通文本_将HTML表转换为可读的纯文本的Python解决方案

用这个怎么样&#xff1a;但是&#xff0c;使用collections.OrderedDict()而不是简单字典来保持顺序。有了字典之后&#xff0c;很容易从中获取和格式化文本&#xff1a;使用Colt 45溶液&#xff1a;import xml.etree.ElementTreeimport collectionss """\Heig…

java synchronized 静态_Java之Synchronized修饰实例方法和静态方法

一、Synchronized修饰实例方法&#xff0c;实际上是对调用该方法的对象加锁&#xff0c;俗称“对象锁”情况一&#xff1a;​同一个对象在两个线程中分别访问该对象的两个同步实例方法结果&#xff1a;会产生互斥​原因&#xff1a;因为锁针对的是对象&#xff0c;当对象调用​…

网站漏洞扫描工具_如何实现免费网站漏洞扫描?推荐一款神器给你

网站漏洞想必有网站的人都比较了解&#xff0c;想要了解网站漏洞&#xff0c;最好的办法就是给网站做一次漏洞扫描&#xff0c;网站漏扫产品比较多&#xff0c;费用也从几十/次到几千/次不等&#xff0c;但是对于我这种小企业来说&#xff0c;几千一次也是非常贵的&#xff0c;…