Scan(0) {..} prints faster than onStart {...} in Flow

suspend fun main() {
    flowOf(1, 2, 3, 4)
        .onEach {
            println("onEach() is called.")
            delay(1000)
        }
        .onStart {
            println("onStart() is called.")
        }
        .scan(0) { acc, v ->
            acc + v
        }
        .onCompletion {
            println("onCompletion() is called.")
        }
        .collect { println(it) }
}

in this above code, the result is…

0 // initial value of scan
onStart() is called.
onEach() is called.
1
onEach() is called.
3
onEach() is called.
6
onEach() is called.
10
onCompletion() is called.

What I expected is that onStart() printed faster than 0. but it wasn’t.
Is it important that how to chain them?

Look at it this way: each step creates a flow. For all except flowOf(...) the new flow is based on another flow.

When a flow is started, it can determine when the underlying flow needs to start. In the case of scan(...), it will first output the initial value, and only then start the underlying flow (the one returned from onStart(...)).

It is even possible that an underlying flow is never started. For example, if you concatenate 2 flows, and then process a number of items equal to or less than the number in the first flow. The second flow will never be started as the concatenation flow does not need items from it.

1 Like

I changed the code to the following snippet below.

suspend fun main() {
    flowOf(1, 2, 3, 4)
        .onEach {
            println("onEach() is called.")
            delay(1000)
        }
        .scan(0) { acc, v ->
            acc + v
        }
        .onStart {
            println("onStart() is called.")
        }
        .onCompletion {
            println("onCompletion() is called.")
        }
        .collect {
            println(it)
        }
}

the result is…

onStart() is called. // onStart() first
0 // initial value of scan
onEach() is called.
1
onEach() is called.
3
onEach() is called.
6
onEach() is called.
10
onCompletion() is called.

This is what i expected.
As you said, each of operators create new flow. when collect{} is called, the flow body will run because Flow is cold stream.
If I understand correctly what you said, the order seems to matter.

Indeed. As you can see in your example onEach(...) is only called 4 times, but there are 5 items being output in total. That is because you invoke onEach(...) at a point where only 4 items will pass.

1 Like