- Asynchronous Flow
- ์ฌ๋ฌ ๊ฐ์ ํ์ํ๋ ๋ฐฉ๋ฒ
- ํ๋ก์ฐ๋ ์ฐจ๊ฐ๋ค(Flows are cold)
- ํ๋ก์ฐ์ ์ทจ์(Flow cancellation basics)
- ํ๋ก์ฐ ๋น๋(Flow Builders)
- ์ค๊ฐ ์ฐ์ฐ์(Intermedicate flow operators)
- ํ๋ก์ฐ ์ข ๋จ ์ฐ์ฐ์(Terminal flow operator)
- ํ๋ก์ฐ๋ ์์ฐจ์ ์ด๋ค(Flow are sequential)
- ํ๋ก์ฐ ์ปจํ ์คํธ(Flow Context)
- ๋ฒํผ๋ง(Buffering)
- ๋ค์ค ํ๋ก์ฐ ํฉ์ฑ(Composing multiple flows)
- ํ๋ก์ฐ ํ๋ํธ๋(Flattening flows)
- ํ๋ก์ฐ ์์ธ(Flow Exception)
- ์์ธ ํฌ๋ช ์ฑ(Exception transparency)
- ํ๋ก์ฐ ์๋ฃ(Flow completion)
- ํ๋ก์ฐ ์คํ(Launching flow)
- ํ๋ก์ฐ ์ทจ์ ํ์ธ(Flow cancellation checks)
๋ชจ๋ ์ฐ์ฐ์ ์ํํ ์ ํ๋ฒ์ List<Int>
๋ฅผ ๋ฐํ ํ๋ค
fun simple(): List<Int> = listOf(1, 2, 3)
fun main() {
simple().forEach { value -> println(value) }
}
์ค๊ฐ์ ๋ฆฌ์คํธ๋ฅผ ๋ง๋ค์ง ์๊ณ ๊ฐ์ ์์ฐจ์ ์ผ๋ก ๊ณ์ฐํ๋ค
fun simple(): Sequence<Int> = sequence { // sequence builder
for (i in 1..3) {
Thread.sleep(100) // pretend we are computing it
yield(i) // yield next value
}
}
fun main() {
simple().forEach { value -> println(value) }
}
listOf(1, 2, 3, 4)
.asSequence()
.map { it * it }
.find { it > 3 }
// list(1) -> map(1*1) -> find(1>3) -> list(2) -> map(2*2) -> find(4>3) -> ์ข
๋ฃ
suspend fun simple(): List<Int> {
delay(1000) // pretend we are doing something asynchronous here
return listOf(1, 2, 3)
}
fun main() = runBlocking<Unit> {
simple().forEach { value -> println(value) }
}
List๊ฐ ๋ชจ๋ ์ฐ์ฐ์ ์ํํ ํ ํ๋ฒ์ ๋ชจ๋ ๊ฐ์ ๋ฐํํ๋๊ฒ๊ณผ ๋ฐ๋๋ก
Flow๋ Sequnce์ ๊ฐ์ด ์์ฐจ์ ์ผ๋ก ๊ฐ์ ๋ด๋ณด๋ด๊ณ ์ ์์ ์ผ๋ก ์๋ฃ ๋๋ ์์ธ๊ฐ ๋ฐ์ํ๋ ๋น๋๊ธฐ ์คํธ๋ฆผ์ด๋ค.
fun simple(): Flow<Int> = flow { // flow builder
for (i in 1..3) {
delay(100) // pretend we are doing something useful here
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
// Launch a concurrent coroutine to check if the main thread is blocked
launch {
for (k in 1..3) {
println("I'm not blocked $k")
delay(100)
}
}
// Collect the flow
simple().collect { value -> println(value) }
}
์ ์ฝ๋๋
- Flow ํ์
์ ์์ฑ์
flow { }
๋น๋๋ฅผ ์ด์ฉํ๋ค flow { ... }
๋ธ๋ก ์์ ์ฝ๋๋ ์ค๋จ ๊ฐ๋ฅํ๋ค- simple() ํจ์๋ ๋์ด์
suspend
๋ก ์ ์ธํ์ง ์๋๋ค - ๊ฒฐ๊ณผ ๊ฐ๋ค์ flow ์์
emit()
ํจ์๋ฅผ ์ด์ฉํ์ฌ ๋ฐฉ์ถ๋๋ค - flow ์์ ๋ฐฉ์ถ๋ ๊ฐ๋ค์
collect
ํจ์๋ฅผ ์ด์ฉํ์ฌ ์์ง๋๋ค - main thread๋ฅผ ์ฐจ๋จํ์ง ์๊ณ
println(value)
ํ๊ธฐ ์ ์ 100ms๋ฅผ ๋๊ธฐํ๋ค
I'm not blocked 1
1
I'm not blocked 2
2
I'm not blocked 3
3
flow๋ sequnce์ ๋น์ทํ๊ฒ cold ์คํธ๋ฆผ์ด๋ค.
flow{ }
๋น๋๊ฐ collect()
๋ฅผ ํธ์ถํ ๋ ๊น์ง ์คํ๋์ง ์๋๋ค.
์ด ๋๋ฌธ์ suspend
๋ก ์ ์ธํ์ง ์์๋ ๋๋๊ฒ์ด๋ค.
flow์ ์ทจ์๋ coroutine์ ์ผ๋ฐ์ ์ธ ํ๋ ฅ์ ์ธ ์ทจ์๋ฅผ ์งํจ๋ค.
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking<Unit> {
withTimeoutOrNull(250) { // Timeout after 250ms
simple().collect { value -> println(value) }
}
println("Done")
}
flow๊ฐ withTimeoutOrNull{ }
์์ ์คํ๋ ๋
Emitting 1
1
Emitting 2
2
Done
- flow { }
- ๊ธฐ๋ณธ์ ์ธ flow Builder
- flowOf(...)
- ๊ณ ์ ๋ ๊ฐ์ ๋ฐฉ์ถํ๋ flow ์ ์
- awFlow()
- ๋ค์ํ Collection, Sequnce๋ฅผ ํ์ฅํจ์๋ฅผ ํตํ์ฌ Flow๋ก ๋ณํ
Collection์ด๋ Sequnce์ ๋์ผํ๊ฒ ์ฐ์ฐ์๋ก ๋ณํํ ์ ์๋ค.
ํ์ง๋ง ์ค์ํ ์ฐจ์ด์ ์ ์ฐ์ฐ์๋ก ์ํ๋๋ ์ฝ๋๋ธ๋ญ์์ suspend
ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค.
์ต์ํ map()
, filter()
๋ฑ์ด ๋ํ์ ์ธ ์์์ด๋ค.
suspend fun performRequest(request: Int): String {
delay(1000) // imitate long-running asynchronous work
return "response $request"
}
fun main() = runBlocking<Unit> {
(1..3).asFlow() // a flow of requests
.map { request -> performRequest(request) }
.collect { response -> println(response) }
ํ๋ก์ฐ ๋ณํ ์ฐ์ฐ์๋ค ์ค์์ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๊ฒ์ transform
์ฐ์ฐ์๋ค.
์ด ์ฐ์ฐ์๋ map
์ด๋ filter()
๊ฐ์ ๋จ์ํ ๋ณํ์ด๋ ํน์ ๋ณต์กํ ๋ค๋ฅธ ๋ณํ๋ค์ ๊ตฌํํ๊ธฐ ์ํด ๋๋ค.
transform()
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๋ฆฌ๋ ์์์ ํ์๋ก ์์์ ๊ฐ๋ค์ ๋ฐฉ์ถํ ์ ์์ต๋๋ค.
suspend fun performRequest(request: Int): String {
delay(1000) // imitate long-running asynchronous work
return "response $request"
}
fun main() = runBlocking<Unit> {
(1..3).asFlow() // a flow of requests
.transform { request ->
emit("Making request $request")
emit(performRequest(request))
}
.collect { response -> println(response) }
}
์๋ฅผ ๋ค์ด, transform
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋ ๊ฑธ๋ฆฌ๋ ๋น๋๊ธฐ ์์ฒญ์ ์ํํ๊ธฐ ์ ์ ๊ธฐ๋ณธ ๋ฌธ์์ด์ ๋จผ์ ๋ฐฉ์ถํ๊ณ
์์ฒญ์ ๋ํ ์๋ต์ด ๋์ฐฉํ๋ฉด ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฉ์ถํ ์ ์๋ค.
Making request 1
response 1
Making request 2
response 2
Making request 3
response 3
take๊ฐ์ ํฌ๊ธฐ ์ ํ ์ค๊ฐ ์ฐ์ฐ์๋ ์ ์๋ ์ ํ์น์ ๋๋ฌํ๋ฉด ์คํ์ ์ทจ์ํ๋ค.
์ฝ๋ฃจํด์์ ์ทจ์๋ ์ธ์ ๋ ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๋ฐฉ์์ผ๋ก ์ํ ๋๋ฉฐ,
์ด๋ฅผ ํตํด try { ... } finally { ... }
๋ก ์์ธ ์ฒ๋ฆฌ๋ฑ์ด ๊ฐ๋ฅํ๋ค.
fun numbers(): Flow<Int> = flow {
try {
emit(1)
emit(2)
println("This line will not execute")
emit(3)
} finally {
println("Finally in numbers")
}
}
fun main() = runBlocking<Unit> {
numbers()
.take(2) // take only the first two
.collect { value -> println(value) }
}
1
2
Finally in numbers
ํ๋ก์ฐ ์์ง์ ์์ํ๋ ์ค๋จ ํจ์์ด๋ค.
collect()
toList()
toSet()
reduce()
fold()
val sum = (1..5).asFlow()
.map { it * it } // squares of numbers from 1 to 5
.reduce { a, b -> a + b } // sum them (terminal operator)
println(sum)
Flow๋ Sequnce์ฒ๋ผ ๊ธฐ๋ณธ์ ์ผ๋ก collect()
๋ฑ์ ์ข
๋จ ์ฐ์ฐ์๊ฐ ํธ์ถ๋ ๋ ์์ฐจ์ ์ผ๋ก ์ฐ์ฐ๋๋ค.
(1..5).asFlow()
.filter {
println("Filter $it")
it % 2 == 0
}
.map {
println("Map $it")
"string $it"
}.collect {
println("Collect $it")
}
Filter 1
Filter 2
Map 2
Collect string 2
Filter 3
Filter 4
Map 4
Collect string 4
Filter 5
flow์ ์์ง(์ข
๋จํจ์ ํธ์ถ)์ ํญ์ CoroutineContext์์์ ์ํ๋๋ค.
์ด๋ฅผ ์ปจํ
์คํธ ๋ณด์กด(context preservation) ์ด๋ผ ํ๋ค.
fun simple(): Flow<Int> = flow {
log("Started simple flow")
for (i in 1..3) {
emit(i)
}
}
fun main() = runBlocking<Unit> {
simple().collect { value -> log("Collected $value") }
}
[main @coroutine#1] Started simple flow
[main @coroutine#1] Collected 1
[main @coroutine#1] Collected 2
[main @coroutine#1] Collected 3
flow{ }
๋ธ๋ญ ๋ด์์ withContext
๋ก CoroutineContext๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์๋๋ค.
fun simple(): Flow<Int> = flow {
kotlinx.coroutines.withContext(Dispatchers.Default) {
for (i in 1..3) {
Thread.sleep(100)
emit(i)
}
}
}
fun main() = runBlocking<Unit> {
simple().collect { value -> println(value) }
}
๋ค์๊ณผ ๊ฐ์ ์๋ฌ ๋ฐ์
Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323],
but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default].
Please refer to 'flow' documentation or use 'flowOn' instead
at ...
flow์์ CoroutineContext๋ฅผ ๋ณ๊ฒฝํ๋ ค๋ฉด flowOn()
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
Thread.sleep(100)
log("Emitting $i")
emit(i) / emit next value
}
}.flowOn(Dispatchers.Default)
fun main() = runBlocking<Unit> {
simple().collect { value ->
log("Collected $value")
}
}
flow {}
์ Background์์ ๋์ํ๋ฉฐ,
๊ทธ ์ดํ collect()
๋ MainThread์์ ์คํ๋๋ค.
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 1
[main @coroutine#1] Collected 1
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 2
[main @coroutine#1] Collected 2
[DefaultDispatcher-worker-1 @coroutine#2] Emitting 3
[main @coroutine#1] Collected 3
flowOn ์ฐ์ฐ์๊ฐ CoroutineDispatcher๋ฅผ ๋ณ๊ฒฝํ buffering ๋งค์ปค๋์ฆ์ ์ฌ์ฉํ๊ฒ๋์ด
flow{ }
์ ๊ธฐ๋ณธ์ ์ธ ํน์ฑ์ธ ์์ฐจ์ฑ์ ์์ด๋ฒ๋ฆฌ๊ฒ ๋ ์ ์๋ค.
๋น๋๊ธฐ ์์ ์ ๊ฒฝ์ฐ์ buffer๋ฅผ ์ฌ์ฉํ์์ ๋ ์๊ฐ์ด ๋จ์ถ๋๋ ๊ฒฝ์ฐ๋ ์๋ค.
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
delay(100) // pretend we are asynchronously waiting 100 ms
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
val time = measureTimeMillis {
simple().collect { value ->
delay(300)
println(value)
}
}
println("Collected in $time ms")
}
(100ms + 300ms) * 3 = 1200ms
์ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค
1
2
3
Collected in 1220 ms
์๋์ ๊ฐ์ด buffer๋ฅผ ์ฌ์ฉํด๋ณด๋ฉด
val time = measureTimeMillis {
simple()
.buffer() // buffer emissions, don't wait
.collect { value ->
delay(300) // pretend we are processing it for 300 ms
println(value)
}
}
println("Collected in $time ms")
์ฒซ ๋ฒ์งธ ์๋ฅผ ์ํด์ 100ms๋ฅผ ๊ธฐ๋ค๋ฆฐ ๋ค
๊ฐ๊ฐ์ ์์ฒ๋ฆฌ๋ฅผ ์ํด์ 300ms๋ฅผ ๊ธฐ๋ค๋ฆฌ๊ฒ ๋๋ค
100ms + (300ms * 3) = 1000ms
1
2
3
Collected in 1034 ms
๋ํ flowOn ์ฐ์ฐ์๊ฐ flowOn ์ฐ์ฐ์๊ฐ CoroutineDispatcher๋ฅผ ๋ณ๊ฒฝํ ๊ฒฝ์ฐ ๋์ผํ ๋ฒํผ๋ง ๋งค์ปค๋์ฆ์ ์ฌ์ฉํ๋ค.
flow๊ฐ ์ฐ์ฐ์ ์ผ๋ถ๋ถ์ด๋, ์ํ์ ์
๋ฐ์ดํธ๋ง์ ์ฒ๋ฆฌํด์ผ ํ ๊ฒฝ์ฐ conflate()
๋ฅผ ๋ณํฉ์ ์ฌ์ฉํ ์ ์๋ค.
conflate()
์ ์ฌ์ฉํ์ฌ collect()
์ ์ฒ๋ฆฌ๊ฐ ๋๋ฌด ๋๋ฆด ๊ฒฝ์ฐ ๋ฐฉ์ถ๋ ์ค๊ฐ ๊ฐ์ ์คํตํ ์ ์๋ค.
val time = measureTimeMillis {
simple()
.conflate() // conflate emissions, don't process each one
.collect { value ->
delay(300) // pretend we are processing it for 300 ms
println(value)
}
}
println("Collected in $time ms")
๋ ๋ฒ์งธ ์๋ฅผ ์คํตํ๊ณ ๊ฐ์ฅ ์ต๊ทผ ๊ฐ์ธ ์ธ ๋ฒ์งธ ์๊ฐ collect()
๋ก ์ ๋ฌ๋๋ค.
1
3
Collected in 758 ms
์ค๊ฐ๊ฐ์ ๋ชจ๋ ์ญ์ ํ์ฌ ์ต์ ์ ๊ฐ๋ง์ ์ฒ๋ฆฌํ์ฌ ์๋๋ฅผ ๋์ด๋ ๋ฐฉ๋ฒ๋ ์๋ค.
collectLatest()
๋ฅผ ์ฌ์ฉํ์ฌ
์๋ก์ด ๊ฐ์ด emit๋ ๋ ๋ง๋ค ๊ธฐ์กด์ collect
์์
์ ์ทจ์ํ๊ณ ์ฌ์์ ํ๋ค.
val time = measureTimeMillis {
simple()
.collectLatest { value -> // cancel & restart on the latest value
println("Collecting $value")
delay(300) // pretend we are processing it for 300 ms
println("Done $value")
}
}
println("Collected in $time ms")
๋ง์ง๋ง ๊ฐ 3์ ๋ํด์๋ง collect
๋ฅผ ๋๊น์ง ์ํํ๋ค.
์ฝ (100ms * 3) + 300ms = 600ms
์ ์๊ฐ์ด ์์๋๋ค.
Collecting 1
Collecting 2
Collecting 3
Done 3
Collected in 677 ms
์ฌ๋ฌ๊ฐ์ง flow๋ฅผ ํฉ์ฑํ๋ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
val nums = (1..3).asFlow()
val strs = flowOf("one", "two", "three")
nums.zip(strs) { a, b -> "$a -> $b" }
.collect { println(it) }
๋ flow์์ ๋ฐฉ์ถ์ด ์ผ์ด๋ ๋๋ง๋ค ๋ค๋ฅธ flow์ ์ต์ ๊ฐ์ ๊ฐ์ง๊ณ ๋ณํฉํ์ฌ ์ถ๋ ฅ.
val nums = (1..3).asFlow().onEach { delay(300) }
val strs = flowOf("one", "two", "three").onEach { delay(400) }
val startTime = System.currentTimeMillis()
nums.combine(strs) { a, b -> "$a -> $b" }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
1 -> one at 452 ms from start
2 -> one at 651 ms from start
2 -> two at 854 ms from start
3 -> two at 952 ms from start
3 -> three at 1256 ms from start
flow{ flow{ } }
์ฒ๋ผ flow๊ฐ ์ค์ฒฉ๋๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. ( Flow<Flow<String>>
)
์๋ฅผ๋ค์ด
fun requestFlow(i: Int): Flow<String> = flow {
emit("$i: First")
delay(500) // wait 500 ms
emit("$i: Second")
}
(1..3).asFlow().map { requestFlow(it) }
์ด๋ฌํ ๊ฒฝ์ฐ flattening์ด ์ฐ์ฐ์๋ก flattning์ด ํ์ํ๋ค
flatMapConcat()
์ ํ์ฌ flow๊ฐ ์๋ฃ๋ ํ ๊ทธ ๋ค์ flow๋ฅผ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ณ ๊ฒฐ๊ณผ๋ก Flow๋ฅผ ๋ฐํํ๋ค
val startTime = System.currentTimeMillis()
(1..3).asFlow().onEach { delay(100)
.flatMapConcat { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
1: First at 121 ms from start
1: Second at 622 ms from start
2: First at 727 ms from start
2: Second at 1227 ms from start
3: First at 1328 ms from start
3: Second at 1829 ms from start
flatMapMerge()
๋ ๋ flow๋ฅผ ๋์์ ์์งํ๊ณ ๊ฐ๋ฅํ ๋นจ๋ฆฌ ๊ฐ์ ๋ฐฉ์ถํ๋๋ก ํ๋ค.
val startTime = System.currentTimeMillis()
(1..3).asFlow().onEach { delay(100) }
.flatMapMerge { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
1: First at 136 ms from start
2: First at 231 ms from start
3: First at 333 ms from start
1: Second at 639 ms from start
2: Second at 732 ms from start
3: Second at 833 ms from start
flatMapLatest()
๋ ์๋ก์ด flow๊ฐ ๋ฐฉ์ถ๋ ๋๋ง๋ค ์ง์ flow๋ฅผ ์ทจ์ํ๋ค.
val startTime = System.currentTimeMillis() // remember the start time
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms
.flatMapLatest { requestFlow(it) }
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
์ ๊ฐ์ด ๋ฐฉ์ถ๋๋ฉด ํ์ฌ ์งํ์ค์ธ { requestFlow(it) }
๋ฅผ ์ทจ์ํ๋ค.
1: First at 142 ms from start
2: First at 322 ms from start
3: First at 425 ms from start
3: Second at 931 ms from start
flow๋ ๋ธ๋ญ ์์์ ์ฝ๋๊ฐ ์์ธ๋ฅผ ๋ฐ์์ํค๋ฉด ์์ธ ๋ฐ์ ์ํ๋ก ์ข
๋ฃ๋๋ค.
์์ธ์ฒ๋ฆฌ์ ๋ํ์ฌ ์์๋ณด์
collector์์ try{ } catch{ }
๋ฅผ ์ฌ์ฉ
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // emit next value
}
}
fun main() = runBlocking<Unit> {
try {
simple().collect { value ->
println(value)
check(value <= 1) { "Collected $value" }
}
} catch (e: Throwable) {
println("Caught $e")
}
}
Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException: Collected 2
flow{ }
์ด๋, ์ค๊ฐ์ฐ์ฐ์, ์ข
๋จ์ฐ์ฐ์ ๋ฑ์์ ๋ฐ์ํ๋ ๋ชจ๋ ์๋ฌ๋ try{ } catch{ }
๋ก ์์ธ์ฒ๋ฆฌ ๊ฐ๋ฅ
fun simple(): Flow<String> =
flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // emit next value
}
}
.map { value ->
check(value <= 1) { "Crashed on $value" }
"string $value"
}
fun main() = runBlocking<Unit> {
try {
simple().collect { value -> println(value) }
} catch (e: Throwable) {
println("Caught $e")
}
}
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
์์ try{ } catch{ }
๋ฅผ ์ฌ์ฉํ๋๊ฒ์ ์์ธํฌ๋ช
์ฑ์ ์๋ฐํ๋ ๊ฒ์ด๋ค.
์์ธ ํฌ๋ช
์ฑ์ ๋ณด์กดํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ผ๋ก ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ด ์๋ค.
throw
์ฐ์ฐ์๋ฅผ ํตํ ์์ธ ๋ค์ ๋์ง๊ธฐcatch()
๋ก์ง์์emit()
์ ์ฌ์ฉํ์ฌ ๊ฐ ํ์ ์ผ๋ก ๋ฐฉ์ถ- ๋ค๋ฅธ ์ฝ๋๋ฅผ ํตํ ์์ธ ๋ฌด์, ๋ก๊น , ๊ธฐํ ์ฒ๋ฆฌ
catch()
๋ ์
์คํธ๋ฆผ์์ ๋ฐ์ํ ์์ธ๋ง์ ์ฒ๋ฆฌํ๋ค.
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking<Unit> {
simple()
.catch { e -> println("Caught $e") } // does not catch downstream exceptions
.collect { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
}
Emitting 1
1
Emitting 2
Exception in thread "main" java.lang.IllegalStateException: Collected 2
at FileKt$main$1$invokeSuspend$$inlined$collect$1.emit (Collect.kt:136)
....
simple()
.onEach { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
.catch { e -> println("Caught $e") }
.collect()
Emitting 1
1
Emitting 2
Caught java.lang.IllegalStateException: Collected 2
flow์ ์์ง์ด ์ข ๋ฃ(์ ์์ข ๋ฃ or ์์ธ๋ฐ์)๋์์ ๋ ๊ทธ ์ดํ ๋์์ ์ฒ๋ฆฌํด์ผ ํ ๋ ๊ฐ ์๋ค
onCompletion()
์ค๊ฐ ์ฐ์ฐ์๋ฅผ ์ถ๊ฐํ์ฌ
flow๊ฐ ์์ ํ ์์ง๋์์๋ ์คํํ ๋ก์ง์ ์ ์ํ ์ ์๋ค.
simple()
.onCompletion { println("Done") }
.collect { value -> println(value) }
onCompletion
์ ์ฌ์ฉํจ์ผ๋ก์จ ์ป์ ์ ์๋ ์ต๋์ ์ด์ ์
๋๋ค์ nullable๋ก ์ ์๋๋ Throwable ํ๋ผ๋ฏธํฐ๋ฅผ ์ด์ฉํด ์์ง์ด ์ฑ๊ณต์ ์ผ๋ก ์ข
๋ฃ๋์๋์ง ์ ์ ์๋ค๋ ์ ์ด๋ค.
fun simple(): Flow<Int> = flow {
emit(1)
throw RuntimeException()
}
fun main() = runBlocking<Unit> {
simple()
.onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
.catch { cause -> println("Caught exception") }
.collect { value -> println(value) }
}
onCompletion()
์ฐ์ฐ์๋ catch()
์ ๋ฌ๋ฆฌ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ง ์๋๋ค.
์์ธ๋ ๋ค์ด์คํธ๋ฆผ์ผ๋ก ๊ณ์ ์ ๋ฌ๋๋ค.
1
Flow completed exceptionally
Caught exception
๋ค์ด์คํธ๋ฆผ์์ ์์ธ๊ฐ ๋ฐ์ํ ์ Throwable์ null์ด๋ค.
fun simple(): Flow<Int> = (1..3).asFlow()
fun main() = runBlocking<Unit> {
simple()
.onCompletion { cause -> println("Flow completed with $cause") }
.collect { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
}
1
Flow completed with
java.lang.IllegalStateException: Collected 2
Exception in thread "main" java.lang.IllegalStateException: Collected 2
flow์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ค์ ๋์ํ๋ ์ฒ๋ฆฌ๋ฅผ ๊ฐ๊ฐ ํด์ผํ๋ค๋ฉด
์ค๊ฐ ์ฐ์ฐ์ onEach()
๋ฅผ ์ฌ์ฉํ๋ค.
fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
fun main() = runBlocking<Unit> {
events()
.onEach { event -> println("Event: $event") }
.collect() // <--- Collecting the flow waits
println("Done")
}
Event: 1
Event: 2
Event: 3
Done
์ค๊ฐ ์ฐ์ฌ์ ์ด๋ฏ๋ก collect()
๋ฅผ ํธ์ถํ์ง ์์ผ๋ฉด ์์ง๋์ง์๋๋ค.
ํ์ง๋ง ์ด๋ launchIn()
์ ์ฌ์ฉํ๋ฉด
flow์ ์์ง์ ๋ค๋ฅธ Coroutine์์ ์ํํ ์ ์์ผ๋ฉฐ
์ด๋ฅผ ํตํด ์ดํ ์์ฑ๋ ์ฝ๋๋ค์ด ๊ณง๋ฐ๋ก ์คํ๋๋๋ก ํ ์ ์๋ค.
fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }
fun main() = runBlocking<Unit> {
events()
.onEach { event -> println("Event: $event") }
.launchIn(this) // <--- Launching the flow in a separate coroutine
println("Done")
}
Done
Event: 1
Event: 2
Event: 3
launchIn()
์ ๋ฐ๋์ ํ์ํ ์ธ์๋ CoroutineScope์ด๋ค.
๋ํ launchIn()
์ Job์ ๋ฐํํ๋ค.
flow { }
๋ ๋ด๋ณด๋ธ ๊ฐ ๊ฐ์ ๋ํ์ฌ ์์ฒด์ ์ผ๋ก ensureActive
๊ฒ์ฌ๋ฅผ ์ํํ๋ค.
fun foo(): Flow<Int> = flow {
for (i in 1..5) {
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking<Unit> {
foo().collect { value ->
if (value == 3) cancel()
println(value)
}
}
3๊น์ง ์ซ์๋ฅผ ๋ฐฉ์ถํ์๊ณ 4๋ฒ์งธ๋ฅผ ๋ฐฉ์ถํ๊ณ ๋๋ค์๋ collect
๋ฅผ ์คํํ ์ ์์ผ๋ฏ๋ก
์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
Emitting 1
1
Emitting 2
2
Emitting 3
3
Emitting 4
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c
ํ์ง๋ง asFlow()
์ ๊ฐ์ ๋๋ถ๋ถ์ ๋ค๋ฅธ ์ฐ์ฐ์๋ค์
์ฑ๋ฅ์์ ์ด์ ๋ก ์์ฒด์ ์ผ๋ก ์ถ๊ฐ ์ทจ์ ํ์ธ์ ์ํํ์ง ์๋๋ค.
fun main() = runBlocking<Unit> {
(1..5).asFlow().collect { value ->
if (value == 3) cancel()
println(value)
}
}
1~5๊น์ง ๋ชจ๋ ์ซ์๊ฐ ์์ง๋๊ณ runBlocking
์ด ๋ฐํ๋๊ธฐ ์ ์ ์ทจ์๊ฐ ๊ฐ์ง๋๋ค
1
2
3
4
5
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23
cancellable()
์ ์ฌ์ฉํ๋ฉด .onEach{ currentCoroutineContext().ensureActive() }
๋ฅผ ์ํํ์ฌ
1~3๊น์ง ์ซ์๋ง ์์ง๋๋ค
fun main() = runBlocking<Unit> {
(1..5).asFlow().cancellable().collect { value ->
if (value == 3) cancel()
println(value)
}
}
1
2
3
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365