Why does the andThen of Future not chain the result?
Asked Answered
M

2

26

The andThen meaning I learned from this answer is a function composer.

Say that

f andThen g andThen h

will equal to

h(g(f(x)))

This implies the h function will receive input from g(f(x))

But for the andThen in Future, all the closure of the following andThen always receives the result from the original Future.

Future{
    1
}.andThen{ case Success(x) =>
    println(x) // print 1
    Thread.sleep(2000)
    x * 2
}.andThen{ case Success(x) =>
    println(x) // print 1
    Thread.sleep(2000)
    x * 2
}

compare to

val func: Function1[Int, Int] = { x: Int =>
  x
}.andThen { y =>
  println(y) // print 1
  y * 2
}.andThen { z =>
  println(z) // print 2
  z * 2
}
func(1)

What is the reason to make Future::andThen(s) receive all the same result from original Future instead of chaining Future? I've observed that these chained andThen will be executed sequentially, so the reason may not be for parallel purpose.

Magalymagan answered 5/6, 2015 at 6:7 Comment(0)
I
33

scala.concurrent.Future is designed as compromise of two asynchronous approaches:

  1. Object-oriented observer which allows binding of asynchronous handlers
  2. Functional monad which offers rich functional composition capabilities.

Reading Future.andThen's docs:

Applies the side-effecting function to the result of this future, and returns a new future with the result of this future.

So andThen is most likely from OOP universe. To gain similar similar result to Function1.andThen you could use map method :

Future(1).map {_ * 2}.map {_ * 2}

andThen differs from onComplete with one little thing: resulting Future of andThen still returning same result, but will wait until supplied observer will return or throw something. That's why there is written in the docs:

This method allows one to enforce that the callbacks are executed in a specified order.

Also note third line from docs:

Note that if one of the chained andThen callbacks throws an exception, that exception is not propagated to the subsequent andThen callbacks. Instead, the subsequent andThen callbacks are given the original value of this future.

So it' completely do nothing with new Future's result. Could not even spoil it with it's ownt exception. This andThen and onComplete just sequential and parallel binding of observers.

Intermingle answered 5/6, 2015 at 9:38 Comment(6)
Can I say It is another version of onComplete but return "this" so that it can bind multiple "observer" callback handlers in chaining style?Magalymagan
@ChenOT Yes. Pretty muchIntermingle
@But with one difference. I'll add this to asnwerIntermingle
oh oh! The chain-able andThen, different to onComplete returning Unit, implies these andThen closures will be executed sequentiallyMagalymagan
onSuccess, onFailure, onComplete, and andThen are a group of functions for binding Observer callback function which may be a side effect procedure. And another group of map, flatMap, fallbackTo, recover, ... are a future composer to chain up and propagate the future result to the tail. Am I right?Magalymagan
Let us continue this discussion in chat.Magalymagan
S
2

Let me sum up this nice discussion.

Say, we have tf: Future[T] =..., and two functions, f: T => U and g: U => V

We can do vf: Future[V] = tf map f map g, same asvf: Future[V] = tf map (f andThen g)

In another use case, having fp: PartialFunction[T, U] and gp: PartialFunction[U, V], we can run tf1: Future[T] = tf andThen fp andThen gp - these partial functions will be called on the value that tf produces, with no outside effect - only side effects happen. This sequence waits for fp before calling gp.

Yet another future operation, onComplete, works like this: having f: Try[T] => U, the call tf onComplete f will call f even if the future ended with an error; the result of tf onComplete f is of type Unit.

Also, if your function f produces a Future, you will need to use flatMap.

Stefansson answered 5/4, 2018 at 0:51 Comment(1)
Here's something I figured out the hard way: tf andThen f where f: T => U returns a Future[T] (i.e. the value of tf) -- not a Future[U] or anything else related to f. I.e. the result of the full expression does not involve a composition with f, even if andThen might suggest that it does. You might say "that's what map/flatMap is for" and you'd be right but that doesn't mean that andThen isn't also misleading. Ideally Future.andThen would be renamed Future.withSideEffect or something.Fatal

© 2022 - 2024 — McMap. All rights reserved.