在scala中可以方便的实现异步操作,这里是通过Future来实现的,和java中的Future很相似,但是功能更加强大。

定义返回Future的方法

下面我们看下如何定义一个返回Future的方法:

println("Step 1: Define a method which returns a Future")
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def donutStock(donut: String): Future[Int] = Future {
// assume some long running database operation
println("checking donut stock")
10
}

注意这里需要引入scala.concurrent.ExecutionContext.Implicits.global, 它会提供一个默认的线程池来异步执行Future。

阻塞方式获取Future的值

  println("\nStep 2: Call method which returns a Future")
import scala.concurrent.Await
import scala.concurrent.duration._
val vanillaDonutStock = Await.result(donutStock("vanilla donut"), 5 seconds)
println(s"Stock of vanilla donut = $vanillaDonutStock")

donutStock() 是异步执行的,我们可以使用Await.result() 来阻塞主线程来等待donutStock()的执行结果。

下面是其输出:


Step 2: Call method which returns a Future
checking donut stock
Stock of vanilla donut = 10

非阻塞方式获取Future的值

我们可以使用Future.onComplete() 回调来实现非阻塞的通知:

println("\nStep 2: Non blocking future result")
import scala.util.{Failure, Success}
donutStock("vanilla donut").onComplete {
case Success(stock) => println(s"Stock for vanilla donut = $stock")
case Failure(e) => println(s"Failed to find vanilla donut stock, exception = $e")
}
Thread.sleep(3000)

Future.onComplete() 有两种可能情况,Success 或者 Failure,需要引入: import scala.util.{Failure, Success}。

Future链

有时候我们需要在获得一个Future之后再继续对其进行操作,有点类似于java中的管道,下面看一个例子:

println("\nStep 2: Define another method which returns a Future")
def buyDonuts(quantity: Int): Future[Boolean] = Future {
println(s"buying $quantity donuts")
true
}

上面我们又定义了一个方法,用来接收donutStock()的返回值,然后再返回一个Future[Boolean] 。

我们看下使用flatmap该怎么链接他们:

println("\nStep 3: Chaining Futures using flatMap")
val buyingDonuts: Future[Boolean] = donutStock("plain donut").flatMap(qty => buyDonuts(qty))
import scala.concurrent.Await
import scala.concurrent.duration._
val isSuccess = Await.result(buyingDonuts, 5 seconds)
println(s"Buying vanilla donut was successful = $isSuccess")

同样的,我们还可以使用for语句来进行链接:


println("\nStep 3: Chaining Futures using for comprehension")
for {
stock <- donutStock("vanilla donut")
isSuccess <- buyDonuts(stock)
} yield println(s"Buying vanilla donut was successful = $isSuccess") Thread.sleep(3000)

flatmap VS map

map就是对集合中的元素进行重映射,而flatmap则会将返回的值拆散然后重新组合。 下面举个直观的例子:

val buyingDonuts: Future[Boolean] = donutStock("plain donut").flatMap(qty => buyDonuts(qty))

flatMap返回的值是Future[Boolean]。

val buyingDonuts: Future[Future[Boolean]] = donutStock("plain donut").Map(qty => buyDonuts(qty))

map返回的值是Future[Future[Boolean]]。

Future.sequence() VS Future.traverse()

如果我们有很多个Future,然后想让他们并行执行,则可以使用 Future.sequence() 。

println(s"\nStep 2: Create a List of future operations")
val futureOperations = List(
donutStock("vanilla donut"),
donutStock("plain donut"),
donutStock("chocolate donut")
) println(s"\nStep 5: Call Future.sequence to run the future operations in parallel")
val futureSequenceResults = Future.sequence(futureOperations)
futureSequenceResults.onComplete {
case Success(results) => println(s"Results $results")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

Future.traverse() 和Future.sequence() 类似, 唯一不同的是,Future.traverse()可以对要执行的Future进行操作,如下所示:

println(s"\nStep 3: Call Future.traverse to convert all Option of Int into Int")
val futureTraverseResult = Future.traverse(futureOperations){ futureSomeQty =>
futureSomeQty.map(someQty => someQty.getOrElse(0))
}
futureTraverseResult.onComplete {
case Success(results) => println(s"Results $results")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

Future.foldLeft VS Future reduceLeft

foldLeft 和 reduceLeft 都是用来从左到右做集合操作的,区别在于foldLeft可以提供默认值。看下下面的例子:

println(s"\nStep 3: Call Future.foldLeft to fold over futures results from left to right")
val futureFoldLeft = Future.foldLeft(futureOperations)(0){ case (acc, someQty) =>
acc + someQty.getOrElse(0)
}
futureFoldLeft.onComplete {
case Success(results) => println(s"Results $results")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

输出结果:


Step 3: Call Future.foldLeft to fold over futures results from left to right
Results 20
println(s"\nStep 3: Call Future.reduceLeft to fold over futures results from left to right")
val futureFoldLeft = Future.reduceLeft(futureOperations){ case (acc, someQty) =>
acc.map(qty => qty + someQty.getOrElse(0))
}
futureFoldLeft.onComplete {
case Success(results) => println(s"Results $results")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

输出结果:

Step 3: Call Future.reduceLeft to fold over futures results from left to right
Results Some(20)

Future firstCompletedOf

firstCompletedOf在处理多个Future请求时,会返回第一个处理完成的future结果。

println(s"\nStep 3: Call Future.firstCompletedOf to get the results of the first future that completes")
val futureFirstCompletedResult = Future.firstCompletedOf(futureOperations)
futureFirstCompletedResult.onComplete {
case Success(results) => println(s"Results $results")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

Future zip VS zipWith

zip用来将两个future结果组合成一个tuple. zipWith则可以自定义Function来处理future返回的结果。

println(s"\nStep 3: Zip the values of the first future with the second future")
val donutStockAndPriceOperation = donutStock("vanilla donut") zip donutPrice()
donutStockAndPriceOperation.onComplete {
case Success(results) => println(s"Results $results")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

输出值:


Step 3: Zip the values of the first future with the second future
checking donut stock
Results (Some(10),3.25)

使用zipwith的例子:

println(s"\nStep 4: Call Future.zipWith and pass-through function qtyAndPriceF")
val donutAndPriceOperation = donutStock("vanilla donut").zipWith(donutPrice())(qtyAndPriceF)
donutAndPriceOperation.onComplete {
case Success(result) => println(s"Result $result")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

输出结果:

Step 4: Call Future.zipWith and pass-through function qtyAndPriceF
checking donut stock
Result (10,3.25)

Future andThen

andThen后面可以跟一个自定义的PartialFunction,来处理Future返回的结果, 如下所示:

println(s"\nStep 2: Call Future.andThen with a PartialFunction")
val donutStockOperation = donutStock("vanilla donut")
donutStockOperation.andThen { case stockQty => println(s"Donut stock qty = $stockQty")}

输出结果:

Step 2: Call Future.andThen with a PartialFunction
checking donut stock
Donut stock qty = Success(10)

自定义threadpool

上面的例子中, 我们都是使用了scala的全局ExecutionContext: scala.concurrent.ExecutionContext.Implicits.global.

同样的,我们也可以自定义你自己的ExecutionContext。下面是一个使用java.util.concurrent.Executors的例子:

  println("Step 1: Define an ExecutionContext")
val executor = Executors.newSingleThreadExecutor()
implicit val ec = scala.concurrent.ExecutionContext.fromExecutor(executor) println("\nStep 2: Define a method which returns a Future")
import scala.concurrent.Future
def donutStock(donut: String): Future[Int] = Future {
// assume some long running database operation
println("checking donut stock")
10
} println("\nStep 3: Call method which returns a Future")
val donutStockOperation = donutStock("vanilla donut")
donutStockOperation.onComplete {
case Success(donutStock) => println(s"Results $donutStock")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
} Thread.sleep(3000)
executor.shutdownNow()

recover() recoverWith() and fallbackTo()

这三个方法主要用来处理异常的,recover是用来从你已知的异常中恢复,如下所示:

println("\nStep 3: Call Future.recover to recover from a known exception")
donutStock("unknown donut")
.recover { case e: IllegalStateException if e.getMessage == "Out of stock" => 0 }
.onComplete {
case Success(donutStock) => println(s"Results $donutStock")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

recoverWith()和recover()类似,不同的是他的返回值是一个Future。

println("\nStep 3: Call Future.recoverWith to recover from a known exception")
donutStock("unknown donut")
.recoverWith { case e: IllegalStateException if e.getMessage == "Out of stock" => Future.successful(0) }
.onComplete {
case Success(donutStock) => println(s"Results $donutStock")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

fallbackTo()是在发生异常时,去调用指定的方法:

println("\nStep 3: Call Future.fallbackTo")
val donutStockOperation = donutStock("plain donut")
.fallbackTo(similarDonutStock("vanilla donut"))
.onComplete {
case Success(donutStock) => println(s"Results $donutStock")
case Failure(e) => println(s"Error processing future operations, error = ${e.getMessage}")
}

promise

熟悉ES6的同学可能知道,promise是JS在ES6中引入的新特性,其主要目的是将回调转变成链式调动。

当然scala的promise和ES6的promise还是不一样的,我们看下scala中promise是怎么用的:

  println("Step 1: Define a method which returns a Future")
import scala.concurrent.ExecutionContext.Implicits.global
def donutStock(donut: String): Int = {
if(donut == "vanilla donut") 10
else throw new IllegalStateException("Out of stock")
} println(s"\nStep 2: Define a Promise of type Int")
val donutStockPromise = Promise[Int]() println("\nStep 3: Define a future from Promise")
val donutStockFuture = donutStockPromise.future
donutStockFuture.onComplete {
case Success(stock) => println(s"Stock for vanilla donut = $stock")
case Failure(e) => println(s"Failed to find vanilla donut stock, exception = $e")
} println("\nStep 4: Use Promise.success or Promise.failure to control execution of your future")
val donut = "vanilla donut"
if(donut == "vanilla donut") {
donutStockPromise.success(donutStock(donut))
} else {
donutStockPromise.failure(Try(donutStock(donut)).failed.get)
} println("\nStep 5: Completing Promise using Promise.complete() method")
val donutStockPromise2 = Promise[Int]()
val donutStockFuture2 = donutStockPromise2.future
donutStockFuture2.onComplete {
case Success(stock) => println(s"Stock for vanilla donut = $stock")
case Failure(e) => println(s"Failed to find vanilla donut stock, exception = $e")
}
donutStockPromise2.complete(Try(donutStock("unknown donut")))

上面例子中我们使用了 Promise.success, Promise.failure, Promise.complete() 来控制程序的运行。

更多教程请参考 flydean的博客

Scala教程之:Future和Promise的更多相关文章

  1. scala教程之:可见性规则

    文章目录 public Protected private scoped private 和 scoped protected 和java很类似,scala也有自己的可见性规则,不同的是scala只有 ...

  2. Scala教程之:深入理解协变和逆变

    文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...

  3. Scala教程之:Either

    在之前的文章中我们提到了Option,scala中Option表示存在0或者1个元素,如果在处理异常的时候Option就会有很大的限制,因为Option如果返回None,那么我并不知道具体的异常到底是 ...

  4. Scala教程之:可变和不变集合

    文章目录 mutable HashMap immutable HashMap 集合在程序中是非常有用的,只有用好集合才能真正感受到该语言的魅力.在scala中集合主要在三个包里面:scala.coll ...

  5. Scala教程之:PartialFunction

    Scala中有一个很有用的traits叫PartialFunction,我看了下别人的翻译叫做偏函数,但是我觉得部分函数更加确切. 那么PartialFunction是做什么用的呢?简单点说Parti ...

  6. Scala教程之:Enumeration

    Enumeration应该算是程序语言里面比较通用的一个类型,在scala中也存在这样的类型, 我们看下Enumeration的定义: abstract class Enumeration (init ...

  7. Scala教程之:Option-Some-None

    文章目录 Option和Some Option和None Option和模式匹配 在java 8中,为了避免NullPointerException,引入了Option,在Scala中也有同样的用法. ...

  8. Scala教程之:scala的参数

    文章目录 默认参数值 命名参数 scala的参数有两大特点: 默认参数值 命名参数 默认参数值 在Scala中,可以给参数提供默认值,这样在调用的时候可以忽略这些具有默认值的参数. def log(m ...

  9. Scala教程之:可扩展的scala

    文章目录 隐式类 限制条件 字符串插值 s 字符串插值器 f 插值器 raw 插值器 自定义插值器 Scala是扩展的,Scala提供了一种独特的语言机制来实现这种功能: 隐式类: 允许给已有的类型添 ...

随机推荐

  1. python常用模块 以及第三方导入

    python常用模块 1模块的分类 标准模块(内置模块)( 标准库 )300 第三方模块 18万 pip install 直接通过pip安装 软件一般会被自动安装你python安装目录的这个子目录里 ...

  2. stm32:extern关键字

    参考:http://c.biancheng.net/view/404.html 1.在一个文件中: #include <stdio.h> int max(int x,int y); int ...

  3. 数据挖掘入门系列教程(八点五)之SVM介绍以及从零开始推导公式

    目录 SVM介绍 线性分类 间隔 最大间隔分类器 拉格朗日乘子法(Lagrange multipliers) 拉格朗日乘子法推导 KKT条件(Karush-Kuhn-Tucker Conditions ...

  4. pm2 开机启动egg项目

    1.在服务器上安装PM2 npm install pm2 -g 2.对PM2进行更新 pm2 update 3.进入服务器中egg项目更目录,并新建server.js文件,并在其中写入以下代码 con ...

  5. python socket简介

    一.socket是什么 socket是应用层与TCP/IP协议通信的中间软件抽象层,它是一组接口.在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议隐藏在socket接口后面, ...

  6. Java第二天,类的概念,属性和方法的使用

    上文中我们已近说到过了,Java是一种面向对象的编程语言,对象是用类来创建的,就比如世界上有无数个父亲,但是他们都有一个共同的属性--男人.也就是说某个父亲这个对象属于男人这个类.类是Java必不可少 ...

  7. 文件的读写 - open

    #写文件,r路径\n 空格\t 缩进# fan=open(r'C:/Users/demiyangping_v/Desktop/fan.txt','w', encoding='utf-8')# fan. ...

  8. 数据库里账号的密码,需要怎样安全的存放?—— 密码哈希(Password Hash)

    最早在大学的时候,只知道用 MD5 来存用户的账号的密码,但其实这非常不安全,而所用到的哈希函数,深入挖掘,也发现并不简单-- 一.普通的 Hash 函数 哈希(散列)函数是什么就不赘述了. 1.不推 ...

  9. 实时OLAP分析利器Druid介绍

    文章目录 前言 Druid介绍 主要特性 基础概念 数据格式 数据摄入 数据存储 数据查询 查询类型 架构 运维 OLAP方案对比 使用场景 使用建议 参考 近期主题 前言 项目早期.数据(报表分析) ...

  10. cgdb使用方法

    cgdb --args [exe_name] [arg1] [arg2] [arg3] [...] 进入代码窗口 按ESC键 进入调试窗口 按i键 调试命令 r 运行 n 单步执行(不进入函数) s ...