I'm having this issue where I use a for-comprehension in Scala to chain some Futures, and then get an instance of a class with some of those values. The problem is that the val I assign the value to, is of type Future[MyClass] instead of MyClass and I can't seem to figure out why.
The code is something like this:
val future = someService.someFutureReturningMethod()
val x = for{
a <- future
b <- someOtherService.someOtherFutureMethod(a)
} yield {
MyClass(b.sth, b.sthElse)
}
The problem here is that x ends up being of type Future[MyClass] and not MyClass and I can't seem to figure out why.
That behavior is correct, you can use for comprehension because Future[T] understands flatMap and map methods.
The following code
val futureA = Future.successful(1)
val futureB = Future.successful(2)
val futureC = Future.successful(3)
val x1 = for {
a <- futureA
b <- futureB
c <- futureC
} yield {
a + b + c
}
It is compiled to
val x2 = futureA.flatMap {
a => futureB.flatMap {
b => futureC.map {
c => a + b + c
}
}
}
A call to Future.flatMap or Future.map is a Future. (it is the same with Option, Try, Either, etc)
If you want the result you need to wait for it.
Await.result(x, Duration(10, TimeUnit.SECONDS))
Related
I have a code which is using for-comprehension to run database query :
val totalFeeNoticeAmountFromDB = Future(/..Doing db job../)(executionContext)
val listOfRestrictedFundFromDB = Future(/..Doing db job../)(executionContext)
val res = for {
totalFeeNoticeAmount <- totalFeeNoticeAmountFromDB
listOfRestrictedFund <- listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
We know for running for-comprehension we need to pass implicit execution context.
But in this case I am wanting to pass execution context manually.
What is the way ?
Edited:
val res = for {
totalFeeNoticeAmount <-(?:ExecutionContext) totalFeeNoticeAmountFromDB
listOfRestrictedFund <-(?:ExecutionContext) listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
totalFeeNoticeAmountFromDB and listOfRestrictedFundFromDB are both Future type already initiated.
Is there any way of passing here
<-(?:ExecutionContext) ?
Perhaps consider scala-async which has gained experimental compiler support -Xasync in Scala 2.13.3 where the following for-comprehension
for {
a <- Future(41)
b <- Future(1)
} yield {
a + b
}
can be rewritten as
async {
val a = async(41)(ec)
val b = async(1)(ec)
await(a) + await(b)
}(ec)
where we can pass in execution context ec explicitly without resorting to flatMap/map.
Another hacky option could be better-monadic-for which supports defining implicits inside for-comprehensions
val ec: ExecutionContext = ???
(for {
implicit0(ec: ExecutionContext) <- Future.successful(ec)
a <- Future(41)(ec)
b <- Future(1)(ec)
} yield {
a + b
})(ec)
You can rewrite
val res = for {
totalFeeNoticeAmount <- totalFeeNoticeAmountFromDB
listOfRestrictedFund <- listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
as
val res = totalFeeNoticeAmountFromDB.flatMap(totalFeeNoticeAmount =>
listOfRestrictedFundFromDB.map(listOfRestrictedFund =>
(totalFeeNoticeAmount, listOfRestrictedFund)
)
)
For example if totalFeeNoticeAmountFromDB and listOfRestrictedFundFromDB are Futures then you can pass implicit scala.concurrent.ExecutionContext.Implicits.global explicitly
val res = totalFeeNoticeAmountFromDB.flatMap(totalFeeNoticeAmount =>
listOfRestrictedFundFromDB.map(listOfRestrictedFund =>
(totalFeeNoticeAmount, listOfRestrictedFund)
)(scala.concurrent.ExecutionContext.Implicits.global)
)(scala.concurrent.ExecutionContext.Implicits.global)
I believe the simplest solution to this problem is just to create an auxiliary function.
def foo(implicit ec: ExecutionContext): Future[(Int, Int)] = {
val totalFeeNoticeAmountFromDB = Future(/..Doing db job../)
val listOfRestrictedFundFromDB = Future(/..Doing db job../)
for {
totalFeeNoticeAmount <- totalFeeNoticeAmountFromDB
listOfRestrictedFund <- listOfRestrictedFundFromDB
} yield (totalFeeNoticeAmount, listOfRestrictedFund)
}
That way when you need it you can just call it like this: foo(ec = myExplicitExecutionContext)
Suppose I have this methods:
def callApis(f1, f2, f3):Future[Result] {
for {
a <- Future { f1 }
b <- Future { f2 }
c <- Future { f3 }
} yield Result(a,b,c)
}
If you are familiar with scala you will know that lines in the for block will execute sequentially. More specific, a will be calculated first. Then when we have the result for a, the code will calculate b. Then when we have the result for b, the code will calculate c.
My question is, how can you write a UNIT TEST that ensures that a always be computed before calculating b, and b always be computed before calculating c? My fear is if someone doesn't know much about how futures work in scala. They can accidentally make this code to run asynchronously.
I mean people can accidentally do something like this, this makes a,b,c to be calculated asynchronously (which I don't want people to do):
def callApis(f1, f2, f3):Future[Result] {
val fut1 = Future { f1 }
val fut2 = Future { f2 }
val fut3 = Future { f3 }
for {
a <- fut1
b <- fut2
c <- fut3
} yield Result(a,b,c)
}
Perhaps try defining a single-threaded execution context and require it in blocks that should execute serially. For example,
trait SerialExecutionContext extends ExecutionContext {
val singleThreadPool = Executors.newFixedThreadPool(1, (r: Runnable) => new Thread(r, s"single-thread-pool"))
val serialEc = ExecutionContext.fromExecutor(singleThreadPool)
override def execute(runnable: Runnable): Unit = serialEc.execute(runnable)
override def reportFailure(cause: Throwable): Unit = serialEc.reportFailure(cause)
}
def callApis()(implicit ec: SerialExecutionContext): Future[Result] = {
val fut1 = Future { ...doSomething... }
val fut2 = Future { ...doSomething... }
val fut3 = Future { ...doSomething... }
for {
a <- fut1
b <- fut2
c <- fut3
} yield Result(a,b,c)
}
Now callApis can evaluate only if we can prove at compile time there exists a serial execution context. Since within the body we have only one thread available futures are forced to start only after the previous one finished.
What is wrong with the following code snippet?
val loginInfoFuture: Future[LoginInfo] = credentialsProvider.authenticate(credentials)
for{loginInfo <- loginInfoFuture}{
println("in loginInfo future")
} yield Future{Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))}
I am seeing error in IDE - Error:(239, 17) ';' expected but 'yield' found.
} yield Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))
I tried a similar piece of code on REPL and that seem to work fine.
scala> import scala.concurrent.Future
import scala.concurrent.Future
scala> import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global
scala> val f:Future[Int] = Future{1}
f: scala.concurrent.Future[Int] = Future(Success(1))
scala> for(f1 <- f) yield f1
res0: scala.concurrent.Future[Int] = Future(<not completed>)
scala>
For reference, below is the full function
def signInUser = silhouette.UserAwareAction.async { implicit request => {
val body: AnyContent = request.body
val jsonBody: Option[JsValue] = body.asJson
jsonBody match {
case Some(json) => {
val userSignin: Option[UserSignin] = json.asOpt[UserSignin] //check if json conforms with UserProfile structure
userSignin match {
case Some(signinInfo) => { //format of JSON is correct
//Get signin info from JSON (email and password)
val credentials: Credentials = Credentials(signinInfo.signinInfo.email, signinInfo.signinInfo.password)
val authInfoRepository = new DelegableAuthInfoRepository(userRepo.passwordRepo)
val passwordHasherRegistory = new PasswordHasherRegistry(userRepo.passwordHasher)
val credentialsProvider = new CredentialsProvider(authInfoRepository, passwordHasherRegistory)
for{loginInfo <- loginInfoFuture}{ //for returns unit. Should use yield
println("in loginInfo future")
} yield Future{Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))}
}
case None => { //No signin info found
Future {
Ok(Json.toJson(JsonResultError("Invalid user. No Login info found")))
}
}
}
}
case None => {//NO Body
Future {
Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))
}
}
} //jsonBody match
}//async
}//def signin
for{loginInfo <- loginInfoFuture}{ //for returns unit. Should use yield
println("in loginInfo future")
} yield Future{Ok(Json.toJson(JsonResultError("Invalid Body Type. Need Json")))}
This is invalid. Your for/yield needs to be in the format:
for {
y <- z
x <- y
//etc
} yield {
//whatever
}
The println after the for but before the yield is throwing you. To get the result of the println inside the for/yield, you could to assign it to a value:
for {
y <- z
a = println(y) // will print out every y
x <- y
//etc
} yield {
//whatever
}
for/yield blocks are stupid like that. At least there are work-arounds though!
The following section is from scala's Future documentation:
def foo(): Unit = {
val f = Future { 5 }
val g = Future { 3 }
val h = for {
x: Int <- f // returns Future(5)
y: Int <- g // returns Future(3)
} yield x + y
}
You on the other hand try to do this:
def foo(): Unit = {
val f = Future { 5 }
val g = Future { 3 }
val h = for {
x: Int <- f // returns Future(5)
y: Int <- g // returns Future(3)
} {
println("whatever") // <<<<<<<<<
} yield x + y
}
The extra block of code that I point is what causing the compilation error which you did not add in your scala repl example.
This is how you can print within a Future:
def foo(): Unit = {
val f = Future {
println("5")
5
}
val g = Future {
println("3")
3
}
val h = for {
x: Int <- f // returns Future(5)
y: Int <- g // returns Future(3)
} yield x + y
}
Error:(239, 17) ';' expected but 'yield' found.
simply means that the for loop definition is wrong
So either with yield
for{loginInfo <- loginInfoFuture
//other conditions and statements
} yield //value to be returned
or without yield
for(loginInfo <- loginInfoFuture){
//value updated
}
are correct for loop definitions
I want to create a function similar to the following. Basically the function, say F will create a Future say fut1. When fut1 resolves, then another Future say fut2 should get created inside fut1. The fut2 should return the final value of the function F. The code has to be non-blocking all the way. I have written something like this but the return type is not Future[Int] but Future[Future[Int]]. I understand why this is the case (because map creates a Future) but I am unable to figure out how to return Future[Int] from this code.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def fut:Future[Int] = {
val f1 = Future{ 1 } //create a Future
f1.map (x => { //when f1 finishes, create another future
println(x)
val f2 = Future{ 2 }
f2.map(x=> x) //this creates another Future and thus the return is Future[Future[Int]]
})
}
You can achieve this using flat map or for comprehension.
FlatMap-
def futureFunctionWithFlatMap: Future[Int] = {
val f1 = Future {
1
}
f1.flatMap(x => {
println(x)
val f2 = Future {
2
}
f2.map(x => x)
})
}
For Comprehension
def futureFunctionWithForComprehension: Future[Int] = {
for {
f1 <- Future { 1 }
f2 <- {
println(f1)
Future { 2 }
}
} yield f2
}
Use flatMap
val f1 = Future{ 1 } //create a Future
val f2: Future[Int] = f1.flatMap(x => {
//will be triggered only after x is ready
Future{2}
})
In the following code I have a Play for Scala function with a future inside a yield. I get a compilation error type mismatch; found : scala.concurrent.Future[Nothing] required: play.api.mvc.Result. Shouldn't I flatMap the future inside the yield to return the Ok response?
def f1 = Future { 1 }
def f2 = Future { 2 }
def index = Action.async (parse.json) { request =>
for { x1 <- f1 }
yield {
val f = f2
f.flatMap { result =>
Ok("X")
}
}
}
No, when you say flatMap you are saying that the result returned in the function is a Future[T], which in your example it returns a Result, so just using a map would work, but is not super idiomatic as you have to flatten as you wind up with a Future[Future[Result]]:
(for { x1 <- f1 } yield {
val f = f2
f.map( result =>
Ok("X")
)
}).flatten
What is more idiomatic is to use a for comprehension for the whole thing:
for {
x1 <- f1
x2 <- f2
} yield {
Ok("X")
}