{"componentChunkName":"component---src-containers-post-index-tsx","path":"/backend/kotlin-in-action/17장-플로우_연산자/","result":{"pageContext":{"next":{"id":"4cf91309-fe0d-5841-9f3f-1b78e4544680","html":"<h2 id=\"-161-플로우는-연속적인-값의-스트림을-모델링한다\" style=\"position:relative;\"><a href=\"#-161-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EC%97%B0%EC%86%8D%EC%A0%81%EC%9D%B8-%EA%B0%92%EC%9D%98-%EC%8A%A4%ED%8A%B8%EB%A6%BC%EC%9D%84-%EB%AA%A8%EB%8D%B8%EB%A7%81%ED%95%9C%EB%8B%A4\" aria-label=\" 161 플로우는 연속적인 값의 스트림을 모델링한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 16.1 플로우는 연속적인 값의 스트림을 모델링한다</h2>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun createValues(): List&lt;Int&gt; {\n    return buildList {\n        add(1)\n        delay(1.seconds)\n        add(2)\n        delay(1.seconds)\n        add(3)\n        delay(1.seconds)\n    }\n}\n\nfun main() = runBlocking {\n    val list = createValues()\n    list.forEach {\n        log(it)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>모든 값이 계산 된 후 함수가 값을 반환함.</li>\n<li>함수가 실행을 마칠 때까지 기다리지 않고 값을 사용할 수 있도록 비동기적으로 반환하고 싶을 때 플롱우가 유용하다.</li>\n<li>플로우는 시간이 지남에 따라 나타나는 값과 작업할 수 있게 해주는 코루틴 기반의 추상화다</li>\n</ul>\n<h3 id=\"-1611-플로우를-사용하면-배출되자마자-원소를-처리할-수-있다\" style=\"position:relative;\"><a href=\"#-1611-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4-%EB%B0%B0%EC%B6%9C%EB%90%98%EC%9E%90%EB%A7%88%EC%9E%90-%EC%9B%90%EC%86%8C%EB%A5%BC-%EC%B2%98%EB%A6%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4\" aria-label=\" 1611 플로우를 사용하면 배출되자마자 원소를 처리할 수 있다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.1.1 플로우를 사용하면 배출되자마자 원소를 처리할 수 있다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun createValues(): Flow&lt;Int&gt; {\n    return flow {\n        emit(1)\n        delay(1.seconds)\n        emit(2)\n        delay(1.seconds)\n        emit(3)\n        delay(1.seconds)\n    }\n}\n\nfun main() = runBlocking {\n    val myFlowOfValues = createValues()\n    myFlowOfValues.collect { log(it) }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] 1\n1036 [main @coroutine#1] 2\n2042 [main @coroutine#1] 3</code>\n        </deckgo-highlight-code>\n<ul>\n<li>원소가 배출되는 즉시 표시된다.</li>\n</ul>\n<h3 id=\"-1612-코틀린-플로우의-여러-유형\" style=\"position:relative;\"><a href=\"#-1612-%EC%BD%94%ED%8B%80%EB%A6%B0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EC%97%AC%EB%9F%AC-%EC%9C%A0%ED%98%95\" aria-label=\" 1612 코틀린 플로우의 여러 유형 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.1.2 코틀린 플로우의 여러 유형</h3>\n<ul>\n<li>콜드 플로우\n<ul>\n<li>비동기 데이터 스트림</li>\n<li>값이 실제로 소비되기 시작할 때만 값을 배출</li>\n</ul>\n</li>\n<li>핫 플로우\n<ul>\n<li>브로드캐스트 방식</li>\n<li>값이 실제로 소비되고 있는지와 상관없이 독립적으로 배출</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"-162-콜드-플로우\" style=\"position:relative;\"><a href=\"#-162-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 162 콜드 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 16.2 콜드 플로우</h2>\n<h3 id=\"-1621-flow-빌더-함수를-사용해-콜드-플로우-생성\" style=\"position:relative;\"><a href=\"#-1621-flow-%EB%B9%8C%EB%8D%94-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%83%9D%EC%84%B1\" aria-label=\" 1621 flow 빌더 함수를 사용해 콜드 플로우 생성 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.1 flow 빌더 함수를 사용해 콜드 플로우 생성</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val letters = flow {\n        log(&quot;Emitting A!&quot;)\n        emit(&quot;A&quot;)\n        delay(200.milliseconds)\n        log(&quot;Emitting B!&quot;)\n        emit(&quot;B&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>아무런 출력도 나타나지 않는다.</li>\n<li><code>flow</code> 빌더 함수를 호출해도 실제 작업이 시작되지 않음.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val counterFlow = flow {\n    var x = 0\n    while (true) {\n        emit(x++)\n        delay(200.milliseconds)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>이 루프는 실제로 플로우가 수집될 때만 실행</li>\n</ul>\n<h3 id=\"-1622-콜드-플로우는-수집되기-전까지-작업을-수행하지-않는다\" style=\"position:relative;\"><a href=\"#-1622-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EC%88%98%EC%A7%91%EB%90%98%EA%B8%B0-%EC%A0%84%EA%B9%8C%EC%A7%80-%EC%9E%91%EC%97%85%EC%9D%84-%EC%88%98%ED%96%89%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4\" aria-label=\" 1622 콜드 플로우는 수집되기 전까지 작업을 수행하지 않는다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.2 콜드 플로우는 수집되기 전까지 작업을 수행하지 않는다</h3>\n<ul>\n<li><code>Flow</code>에 대해 <code>collect</code> 함수를 호출하면 그 로직이 실행된다</li>\n<li>플로우를 수집할 때는 플로우 내부의 일시 중단 코드를 실행하므로 <code>collect</code>는 일시 중단 함수이며, 플로우가 끝날 때까지 일시 중단된다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val letters = flow {\n    log(&quot;Emitting A!&quot;)\n    emit(&quot;A&quot;)\n    delay(200.milliseconds)\n    log(&quot;Emitting B!&quot;)\n    emit(&quot;B&quot;)\n}\n\nfun main() = runBlocking {\n    letters.collect {\n        log(&quot;Collecting: $it&quot;)\n        delay(500.milliseconds)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Emitting A!\n15 [main @coroutine#1] Collecting: A\n737 [main @coroutine#1] Emitting B!\n737 [main @coroutine#1] Collecting: B</code>\n        </deckgo-highlight-code>\n<ul>\n<li>수집자가 플로우의 로직을 실행하는 책임이 있다.</li>\n<li>원소 A, B 사이의 지연시간은 700밀리초이다.\n<ul>\n<li>수집자가 플로우 빌더에 정의된 로직의 실행을 촉발해서 첫 번째 배출 발생</li>\n<li>수집자가 연결된 람다가 호출되면서 메시지를 기록하고 500밀리초 동안 지연</li>\n<li>플로우 람다가 계속 실행되며 200밀리초 동안 추가 지연과 배출이 발생</li>\n</ul>\n</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    letters.collect {\n        log(&quot;(1) Collecting: $it&quot;)\n        delay(500.milliseconds)\n    }\n    letters.collect {\n        log(&quot;(2) Collecting: $it&quot;)\n        delay(500.milliseconds)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Emitting A!\n16 [main @coroutine#1] (1) Collecting: A\n743 [main @coroutine#1] Emitting B!\n743 [main @coroutine#1] (1) Collecting: B\n1247 [main @coroutine#1] Emitting A!\n1247 [main @coroutine#1] (2) Collecting: A\n1958 [main @coroutine#1] Emitting B!\n1958 [main @coroutine#1] (2) Collecting: B</code>\n        </deckgo-highlight-code>\n<ul>\n<li>같은 플로우를 여러 번 수집할 수 있다.</li>\n</ul>\n<h3 id=\"-1623-플로우-수집-취소\" style=\"position:relative;\"><a href=\"#-1623-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%88%98%EC%A7%91-%EC%B7%A8%EC%86%8C\" aria-label=\" 1623 플로우 수집 취소 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.3 플로우 수집 취소</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val collector = launch {\n        counterFlow.collect {\n            println(it)\n        }\n    }\n    delay(5.seconds)\n    collector.cancel()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>수집자의 코루틴을 취소하면 다음 취소 지점에서 플로우의 수집이 중단된다.</li>\n</ul>\n<h3 id=\"-1624-콜드-플로우의-내부-구현\" style=\"position:relative;\"><a href=\"#-1624-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EB%82%B4%EB%B6%80-%EA%B5%AC%ED%98%84\" aria-label=\" 1624 콜드 플로우의 내부 구현 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.4 콜드 플로우의 내부 구현</h3>\n<ul>\n<li>코틀린의 콜드 플로우는 일시 중단 함수와 수신 객체 지정 람다를 결합한 똑똑한 조합이다.</li>\n<li>콜드 플로우의 정의는 매우 간단하다.\n<ul>\n<li><code>Flow</code>와 <code>FlowCollector</code>라는 2가지 인터페이스만 필요</li>\n</ul>\n</li>\n<li><code>collect</code>를 호출하면 플로우 빌더 함수의 본문이 실행</li>\n<li>이 코드가 <code>emit</code>을 호출하면 <code>emit</code>에 전달된 파라미터로 <code>collect</code>에 전달된 람다가 호출</li>\n<li>람다 표현식이 실행을 완료하면 함수는 빌더 함수의 본문으로 돌아가 계속 실행</li>\n<li>콜드 플로우는 값 스트림을 처리하는 가벼우면서도 아주 쓸모 있고 확장성 있는 추상화를 제공</li>\n</ul>\n<h3 id=\"-1625-채널-플로우를-사용한-동시성-플로우\" style=\"position:relative;\"><a href=\"#-1625-%EC%B1%84%EB%84%90-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 1625 채널 플로우를 사용한 동시성 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.2.5 채널 플로우를 사용한 동시성 플로우</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">suspend fun getRandomNumber(): Int {\n    delay(500.milliseconds)\n    return Random.nextInt()\n}\n\nval randomNumbers = flow {\n    repeat(10) {\n        emit(getRandomNumber())\n    }\n}\n\nfun main() = runBlocking {\n    randomNumbers.collect {\n        log(it)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] -1441864547\n513 [main @coroutine#1] 1101626017\n1020 [main @coroutine#1] 313359960\n1527 [main @coroutine#1] -2002910241\n2033 [main @coroutine#1] 890150628\n2538 [main @coroutine#1] -1433970862\n3045 [main @coroutine#1] 1503636281\n3548 [main @coroutine#1] -1528949568\n4055 [main @coroutine#1] -1562012272\n4561 [main @coroutine#1] 2076525050</code>\n        </deckgo-highlight-code>\n<ul>\n<li>플로우는 순차적으로 실행되며, 모든 계산은 동일 코루틴에서 실행</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val randomNumbers = channelFlow {\n    repeat(10) {\n        launch {\n            send(getRandomNumber())\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>동시성으로 호출할 수 있는 플로우 빌더: <code>channelFlow</code></li>\n<li>채널 플로우는 내부적으로 또 다른 동시성 기본 요소인 채널을 관리해야 하기 때문에 생성하는 데 약간 비용이 든다.\n<ul>\n<li>채널은 코루틴 간 통신을 위한 비교적 저수준의 추상화</li>\n</ul>\n</li>\n<li>플로우 안에서 새로운 코루틴을 시작해야 하는 경우에만 채널 플로우 선택</li>\n</ul>\n<h2 id=\"-163-핫-플로우\" style=\"position:relative;\"><a href=\"#-163-%ED%95%AB-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 163 핫 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 16.3 핫 플로우</h2>\n<ul>\n<li>핫 플로우에서는 각 수집자가 플로우 로직 실행을 독립적으로 촉발하는 대신, 여러 구독자라고 불리는 수집자들이 배출된 항목을 공유한다.\n<ul>\n<li>공유 플로우: 값을 브로드캐스트하기 위해 사용</li>\n<li>상태 플로우: 상태를 전달하는 특별한 경우에 사용</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1631-공유-플로우는-값을-구독자에게-브로드캐스트한다\" style=\"position:relative;\"><a href=\"#-1631-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EA%B0%92%EC%9D%84-%EA%B5%AC%EB%8F%85%EC%9E%90%EC%97%90%EA%B2%8C-%EB%B8%8C%EB%A1%9C%EB%93%9C%EC%BA%90%EC%8A%A4%ED%8A%B8%ED%95%9C%EB%8B%A4\" aria-label=\" 1631 공유 플로우는 값을 구독자에게 브로드캐스트한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.1 공유 플로우는 값을 구독자에게 브로드캐스트한다</h3>\n<ul>\n<li>공유 플로우는 구독자가 존재하는지 여부에 상관없이 배출이 발생하는 브로드캐스트 방식</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class RadioStation {\n    private val _messageFlow = MutableSharedFlow&lt;Int&gt;()\n    val messageFlow = _messageFlow.asSharedFlow()\n\n    fun beginBroadcasting(scope: CoroutineScope) {\n        scope.launch {\n            while (true) {\n                delay(500.milliseconds)\n                val number = Random.nextInt(0..10)\n                log(&quot;Emitting $number!&quot;)\n                _messageFlow.emit(number)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>플로우 빌더를 사용하는 대신 가변적인 플로우에 대한 참조를 얻는다.</li>\n<li>배출이 구독자 유무와 관계없이 발생하므로 여러분이 실제 배출을 수행하는 코루틴을 시작할 책임이 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    RadioStation().beginBroadcasting(this)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>구독자가 없어도 브로드캐스트가 즉시 시작</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val radioStation = RadioStation()\n    radioStation.beginBroadcasting(this)\n    delay(600.milliseconds)\n    radioStation.messageFlow.collect {\n        log(&quot;A collecting: $it!&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>약간의 지연 후에 공유 플로우 구독</li>\n</ul>\n<h4 id=\"️-구독자를-위한-값-재생\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-%EA%B5%AC%EB%8F%85%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%92-%EC%9E%AC%EC%83%9D\" aria-label=\"️ 구독자를 위한 값 재생 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ 구독자를 위한 값 재생</h4>\n<ul>\n<li>공유 플로우 구독자는 구독을 시작한 이후에 배출된 값만 수신</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">private val _messageFlow = MutableSharedFlow&lt;Int&gt;(replay = 5)</code>\n        </deckgo-highlight-code>\n<ul>\n<li>이전에 배출된 원소도 수신하기를 원한다면 <code>replay</code> 파라미터 설정</li>\n</ul>\n<h4 id=\"️-sharein으로-콜드-플로우를-공유-플로우로-전환\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-sharein%EC%9C%BC%EB%A1%9C-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A1%9C-%EC%A0%84%ED%99%98\" aria-label=\"️ sharein으로 콜드 플로우를 공유 플로우로 전환 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ shareIn으로 콜드 플로우를 공유 플로우로 전환</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun querySensor(): Int = Random.nextInt(-10..30)\n\nfun getTemperatures(): Flow&lt;Int&gt; {\n    return flow {\n        while (true) {\n            emit(querySensor())\n            delay(500.milliseconds)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>일정한 간격으로 값을 반환</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun celsiusToFahrenheit(celsius: Int) = celsius * 9.0 / 5.0 + 32.0\n\nfun main() {\n    val temps = getTemperatures()\n    runBlocking {\n        launch {\n            temps.collect {\n                log(&quot;$it Celsius&quot;)\n            }\n        }\n        launch {\n            temps.collect {\n                log(&quot;${celsiusToFahrenheit(it)} Fahrenheit&quot;)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>여러번 호출하려면 각 수집자가 센서에 독립적으로 질의</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val temps = getTemperatures()\n    runBlocking {\n        val sharedTemps = temps.shareIn(this, SharingStarted.Lazily)\n        launch {\n            sharedTemps.collect {\n                log(&quot;$it Celsius&quot;)\n            }\n        }\n        launch {\n            sharedTemps.collect {\n                log(&quot;${celsiusToFahrenheit(it)} Fahrenheit&quot;)\n            }\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>shareIn</code> 함수를 사용하면 주어진 콜드 플로우를 한 플로우인 공유 플로우로 변환할 수 있다.</li>\n<li>두번째 파라미터 <code>started</code>는 플로우가 실제로 언제 시작돼야 하는지를 정의\n<ul>\n<li><code>Eagerly</code>는 플로우 수집을 즉시 시작</li>\n<li><code>Lazily</code>는 첫 번째 구독자가 나타나야만 수집 시작</li>\n<li><code>WhileSubscribed</code>는 첫 번째 구독자가 나타나야 수집을 시작하고, 마지막 구독자가 사라지면 플로우 수집을 취소</li>\n</ul>\n</li>\n<li>코틀린에서는 시간이 지남에 따라 여러 값을 계산하는 작업을 단순한 콜드 플로우로 노출하고, 필요할 때 콜드 플로우를 핫 플로우로 변환하는 패턴이 자주 사용된다.</li>\n</ul>\n<h3 id=\"-1632-시스템-상태-추적-상태-플로우\" style=\"position:relative;\"><a href=\"#-1632-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%83%81%ED%83%9C-%EC%B6%94%EC%A0%81-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0\" aria-label=\" 1632 시스템 상태 추적 상태 플로우 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.2 시스템 상태 추적: 상태 플로우</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class ViewCounter {\n    private val _counter = MutableStateFlow(0)\n    val counter = _counter.asStateFlow()\n    fun increment() {\n        _counter.update { it + 1 }\n    }\n}\n\nfun main() {\n    val vc = ViewCounter()\n    vc.increment()\n    println(vc.counter.value)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>값을 배출하는 <code>emit</code>을 사용하는 대신, 값을 갱신하는 <code>update</code> 함수 사용</li>\n</ul>\n<h4 id=\"️-update-함수로-안전하게-상태-플로우에-쓰기\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-update-%ED%95%A8%EC%88%98%EB%A1%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%97%90-%EC%93%B0%EA%B8%B0\" aria-label=\"️ update 함수로 안전하게 상태 플로우에 쓰기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ UPDATE 함수로 안전하게 상태 플로우에 쓰기</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">    fun increment() {\n    _counter.value++\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>value</code> 는 가변 속성이니까 위와 같이 구현해도 될까?</li>\n<li>위 연산은 원자적이지 않다.</li>\n<li>즉, 코루틴들이 여러 스레드에서 실행되기 때문에 문제가 있다.\n<ul>\n<li>어느 한쪽의 증가 연산이 무효화</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"️-상태-플로우는-값이-실제로-달라졌을-때만-값을-배출한다-동등성-기반-통합\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%8A%94-%EA%B0%92%EC%9D%B4-%EC%8B%A4%EC%A0%9C%EB%A1%9C-%EB%8B%AC%EB%9D%BC%EC%A1%8C%EC%9D%84-%EB%95%8C%EB%A7%8C-%EA%B0%92%EC%9D%84-%EB%B0%B0%EC%B6%9C%ED%95%9C%EB%8B%A4-%EB%8F%99%EB%93%B1%EC%84%B1-%EA%B8%B0%EB%B0%98-%ED%86%B5%ED%95%A9\" aria-label=\"️ 상태 플로우는 값이 실제로 달라졌을 때만 값을 배출한다 동등성 기반 통합 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ 상태 플로우는 값이 실제로 달라졌을 때만 값을 배출한다: 동등성 기반 통합</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">enum class Direction { LEFT, RIGHT }\n\nclass DirectionSelector {\n    private val _direction = MutableStateFlow(Direction.LEFT)\n    val direction = _direction.asStateFlow()\n\n    fun turn(d: Direction) {\n        _direction.update { d }\n    }\n}\n\nfun main() = runBlocking {\n    val switch = DirectionSelector()\n    launch {\n        switch.direction.collect {\n            log(&quot;Direction now $it&quot;)\n        }\n    }\n    delay(200.milliseconds)\n    switch.turn(Direction.RIGHT)\n    delay(200.milliseconds)\n    switch.turn(Direction.LEFT)\n    delay(200.milliseconds)\n    switch.turn(Direction.LEFT)\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#2] Direction now LEFT\n210 [main @coroutine#2] Direction now RIGHT\n405 [main @coroutine#2] Direction now LEFT</code>\n        </deckgo-highlight-code>\n<ul>\n<li>LEFT 인자가 한번만 호출이 된다.</li>\n<li>상태 플로우가 동등성 기반 통합을 수행하기 때문\n<ul>\n<li>값이 실제로 달라졌을 때만 구독자에게 값을 배출</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"️-statein-으로-콜드-플로우를-상태-플로우로-변환하기\" style=\"position:relative;\"><a href=\"#%EF%B8%8F-statein-%EC%9C%BC%EB%A1%9C-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0\" aria-label=\"️ statein 으로 콜드 플로우를 상태 플로우로 변환하기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🛠️ stateIn 으로 콜드 플로우를 상태 플로우로 변환하기</h4>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    val temps = getTemperatures()\n\n    runBlocking {\n        val tempState = temps.stateIn(this)\n        println(tempState.value)\n        delay(800.milliseconds)\n        println(tempState.value)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>stateIn</code>으로 콜드 플로우를 상태 플로우로 변환할 수 있다.</li>\n<li>원래 플로우에서 배출된 최신 값을 항상 읽을 수 있다.</li>\n</ul>\n<h3 id=\"-1633-상태-플로우와-공유-플로우의-비교\" style=\"position:relative;\"><a href=\"#-1633-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%99%80-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EB%B9%84%EA%B5%90\" aria-label=\" 1633 상태 플로우와 공유 플로우의 비교 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.3 상태 플로우와 공유 플로우의 비교</h3>\n<ul>\n<li>일반적으로 상태 플로우는 공유 플로우보다 더 간단한 API를 제공</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class Broadcaster {\n    private val _messages = MutableSharedFlow&lt;String&gt;()\n    val messages = _messages.asSharedFlow()\n    fun beginBroadcasting(scope: CoroutineScope) {\n        scope.launch {\n            _messages.emit(&quot;Hello!&quot;)\n            _messages.emit(&quot;Hi!&quot;)\n            _messages.emit(&quot;Hola!&quot;)\n        }\n    }\n}\n\nfun main(): Unit = runBlocking {\n    val broadcaster = Broadcaster()\n    broadcaster.beginBroadcasting(this)\n    delay(200.milliseconds)\n    broadcaster.messages.collect {\n        println(&quot;Message: $it&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>첫 번째 구독자가 나타나기 전에 모든 메시지를 배출하는 브로드캐스트</li>\n<li>메시지를 출력하지 않음.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class Broadcaster {\n    private val _messages = MutableStateFlow&lt;List&lt;String&gt;&gt;(emptyList())\n    val messages = _messages.asStateFlow()\n    fun beginBroadcasting(scope: CoroutineScope) {\n        scope.launch {\n            _messages.update { it + &quot;Hello!&quot; }\n            _messages.update { it + &quot;Hi!&quot; }\n            _messages.update { it + &quot;Hola!&quot; }\n        }\n    }\n}\n\nfun main() = runBlocking {\n    val broadcaster = Broadcaster()\n    broadcaster.beginBroadcasting(this)\n    delay(200.milliseconds)\n    println(broadcaster.messages.value)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>상태 플로우는 전체 메시지 기록을 리스트로 저장하면서 구독자가 모든 이전 메시지에 쉽게 접근할 수 있게 할 수 있다.</li>\n</ul>\n<h3 id=\"-1634-핫-플로우-콜드-플로우-공유-플로우-상태-플로우-언제-어떤-플로우를-사용할까\" style=\"position:relative;\"><a href=\"#-1634-%ED%95%AB-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EA%B3%B5%EC%9C%A0-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%83%81%ED%83%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%96%B8%EC%A0%9C-%EC%96%B4%EB%96%A4-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C\" aria-label=\" 1634 핫 플로우 콜드 플로우 공유 플로우 상태 플로우 언제 어떤 플로우를 사용할까 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 16.3.4 핫 플로우, 콜드 플로우, 공유 플로우, 상태 플로우: 언제 어떤 플로우를 사용할까?</h3>\n<h3 id=\"표-161-콜드-플로우와-핫-플로우-비교\" style=\"position:relative;\"><a href=\"#%ED%91%9C-161-%EC%BD%9C%EB%93%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%99%80-%ED%95%AB-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%B9%84%EA%B5%90\" aria-label=\"표 161 콜드 플로우와 핫 플로우 비교 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>표 16.1 콜드 플로우와 핫 플로우 비교</h3>\n<table>\n<thead>\n<tr>\n<th>콜드 플로우</th>\n<th>핫 플로우</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>기본적으로 비활성(수집자에 의해 활성화됨)</td>\n<td>기본적으로 활성화됨</td>\n</tr>\n<tr>\n<td>수집자가 하나 있음</td>\n<td>여러 구독자가 있음</td>\n</tr>\n<tr>\n<td>수집자는 모든 배출을 받음</td>\n<td>구독자는 구독 시작 시점부터 배출을 받음</td>\n</tr>\n<tr>\n<td>보통은 완료됨</td>\n<td>완료되지 않음</td>\n</tr>\n<tr>\n<td>하나의 코루틴에서 배출 발생(channelFlow 사용 시 예외)</td>\n<td>여러 코루틴에서 배출할 수 있음</td>\n</tr>\n</tbody>\n</table>","excerpt":"📖 16.1 플로우는 연속적인 값의 스트림을 모델링한다 모든 값이 계산 된 후 함수가 값을 반환함. 함수가 실행을 마칠 때까지 기다리지 않고 값을 사용할 수 있도록 비동기적으로 반환하고 싶을 때 플롱우가 유용하다. 플로우는 시간이 지남에 따라 나타나는 값과 작업할 수 있게 해주는 코루틴 기반의 추상화다 🔖 16.1.1 플로우를 사용하면 배출되자마자 원소를 처리할 수 있다 원소가 배출되는 즉시 표시된다. 🔖 16.1.…","fields":{"slug":"/backend/kotlin-in-action/16장-플로우/"},"frontmatter":{"title":"Kotlin in Action - 16장 플로우","thumbnail":{"childImageSharp":{"fluid":{"src":"/static/f7f7ddfa31d1614405dc5af1487b9ec4/9c94d/kotlin-in-action.png"}}},"draft":false,"category":"Back-End","tags":["Kotlin"],"date":"June 22, 2025"}},"previous":{"id":"674cdded-b7f4-544a-8575-a58074fcfdbe","html":"<h2 id=\"-181-코루틴-내부에서-던져진-오류-처리\" style=\"position:relative;\"><a href=\"#-181-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EB%82%B4%EB%B6%80%EC%97%90%EC%84%9C-%EB%8D%98%EC%A0%B8%EC%A7%84-%EC%98%A4%EB%A5%98-%EC%B2%98%EB%A6%AC\" aria-label=\" 181 코루틴 내부에서 던져진 오류 처리 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 18.1 코루틴 내부에서 던져진 오류 처리</h2>\n<ul>\n<li>일시 중단 함수나 코루틴 빌더 안에 작성한 코드도 예외를 발생시킬 수 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    try {\n        launch {\n            throw UnsupportedOperationException(&quot;Ouch!&quot;)\n        }\n    } catch (u: UnsupportedOperationException) {\n        println(&quot;Handled $u&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>코루틴 빌더는 실행할 새로운 코루틴을 생성하는데, 이 새로운 코루틴에서 발생한 예외는 catch 블록에 의해 잡히지 않는다</li>\n<li>위 코드는 예외가 잡히지 않는다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    launch {\n        try {\n            throw UnsupportedOperationException(&quot;Ouch!&quot;)\n        } catch (u: UnsupportedOperationException) {\n            println(&quot;Handled $u&quot;)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>launch</code>에 전달되는 람다 블록안에서는 잡힌다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    val myDeferredInt: Deferred&lt;Int&gt; = async {\n        throw UnsupportedOperationException(&quot;Ouch!&quot;)\n    }\n    try {\n        val i: Int = myDeferredInt.await()\n        println(i)\n    } catch (u: UnsupportedOperationException) {\n        println(&quot;Handled: $u&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>await</code>를 감싼 <code>try-catch</code>에서 예외를 잡지만 동시에 예외를 출력한다.</li>\n<li>자식 코루틴은 잡히지 않은 예외를 항상 부모 코루틴에 전파한다.</li>\n</ul>\n<h2 id=\"-182-코틀린-코루틴에서의-오류-전파\" style=\"position:relative;\"><a href=\"#-182-%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%97%90%EC%84%9C%EC%9D%98-%EC%98%A4%EB%A5%98-%EC%A0%84%ED%8C%8C\" aria-label=\" 182 코틀린 코루틴에서의 오류 전파 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 18.2 코틀린 코루틴에서의 오류 전파</h2>\n<ul>\n<li>한 자식의 실패가 부모의 실패로 이어진다.</li>\n<li>자식의 실패로 인해 시스템 전체가 실패하면서 멈추지는 말아야 하는 경우를, 자식이 부모의 실행을 감독한다고 말한다.</li>\n</ul>\n<h3 id=\"-1821-자식이-실패하면-모든-자식을-취소하는-코루틴\" style=\"position:relative;\"><a href=\"#-1821-%EC%9E%90%EC%8B%9D%EC%9D%B4-%EC%8B%A4%ED%8C%A8%ED%95%98%EB%A9%B4-%EB%AA%A8%EB%93%A0-%EC%9E%90%EC%8B%9D%EC%9D%84-%EC%B7%A8%EC%86%8C%ED%95%98%EB%8A%94-%EC%BD%94%EB%A3%A8%ED%8B%B4\" aria-label=\" 1821 자식이 실패하면 모든 자식을 취소하는 코루틴 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.2.1 자식이 실패하면 모든 자식을 취소하는 코루틴</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    launch {\n        try {\n            while (true) {\n                println(&quot;Heartbeat!&quot;)\n                delay(500.milliseconds)\n            }\n        } catch (e: Exception) {\n            println(&quot;Heartbeat terminated: $e&quot;)\n            throw e\n        }\n    }\n    launch {\n        delay(1.seconds)\n        throw UnsupportedOperationException(&quot;Ow!&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">Heartbeat!\nHeartbeat!\nHeartbeat terminated: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=&quot;coroutine#1&quot;:BlockingCoroutine{Cancelling}@130f889\nException in thread &quot;main&quot; java.lang.UnsupportedOperationException: Ow!</code>\n        </deckgo-highlight-code>\n<ul>\n<li>형제 코루틴 중 하나가 예외를 던지면 하트비트 코루틴도 취소된다.</li>\n<li>같은 스코프 안에서 동시성 계산을 함께 수행하고 공통의 결과를 반환하는 코루틴 그룹에게 아주 유용하다.</li>\n</ul>\n<h3 id=\"-1822-구조적-동시성은-코루틴-스코프를-넘는-예외에만-영향을-미친다\" style=\"position:relative;\"><a href=\"#-1822-%EA%B5%AC%EC%A1%B0%EC%A0%81-%EB%8F%99%EC%8B%9C%EC%84%B1%EC%9D%80-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%8A%A4%EC%BD%94%ED%94%84%EB%A5%BC-%EB%84%98%EB%8A%94-%EC%98%88%EC%99%B8%EC%97%90%EB%A7%8C-%EC%98%81%ED%96%A5%EC%9D%84-%EB%AF%B8%EC%B9%9C%EB%8B%A4\" aria-label=\" 1822 구조적 동시성은 코루틴 스코프를 넘는 예외에만 영향을 미친다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.2.2 구조적 동시성은 코루틴 스코프를 넘는 예외에만 영향을 미친다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    launch {\n        try {\n            while (true) {\n                println(&quot;Heartbeat!&quot;)\n                delay(500.milliseconds)\n            }\n        } catch (e: Exception) {\n            println(&quot;Heartbeat terminated: $e&quot;)\n            throw e\n        }\n    }\n    launch {\n        try {\n            delay(1.seconds)\n            throw UnsupportedOperationException(&quot;Ow!&quot;)\n        } catch (u: UnsupportedOperationException) {\n            println(&quot;Caught $u&quot;)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>예외가 발생한 다음에도 하트비트 코루틴이 계속 텍스트를 출력</li>\n<li>처리되지 않은 예외를 코루틴 계층 위쪽으로 전파하고 형제 코루틴을 취소하는 것은 애플리케이션에서 구조적 동시성 패러다임을 강제하는 데 도움이 된다.</li>\n</ul>\n<h3 id=\"-1823-슈퍼바이저는-부모와-형제가-취소되지-않게-한다\" style=\"position:relative;\"><a href=\"#-1823-%EC%8A%88%ED%8D%BC%EB%B0%94%EC%9D%B4%EC%A0%80%EB%8A%94-%EB%B6%80%EB%AA%A8%EC%99%80-%ED%98%95%EC%A0%9C%EA%B0%80-%EC%B7%A8%EC%86%8C%EB%90%98%EC%A7%80-%EC%95%8A%EA%B2%8C-%ED%95%9C%EB%8B%A4\" aria-label=\" 1823 슈퍼바이저는 부모와 형제가 취소되지 않게 한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.2.3 슈퍼바이저는 부모와 형제가 취소되지 않게 한다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    supervisorScope {\n        launch {\n            try {\n                while (true) {\n                    println(&quot;Heartbeat!&quot;)\n                    delay(500.milliseconds)\n                }\n            } catch (e: Exception) {\n                println(&quot;Heartbeat terminated: $e&quot;)\n                throw e\n            }\n        }\n        launch {\n            delay(1.seconds)\n            throw UnsupportedOperationException(&quot;Ow!&quot;)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>자식 코루틴이 부모 코루틴을 취소하지 못하게 슈퍼바이저가 막는다.</li>\n<li>슈퍼바이저는 자식이 실패하더라도 생존</li>\n<li>슈퍼바이저는 애플리케이션에서 코루틴 계층의 위쪽에 위치하는 경우가 많다.</li>\n</ul>\n<h2 id=\"-183-coroutineexceptionhandler-예외-처리를-위한-마지막-수단\" style=\"position:relative;\"><a href=\"#-183-coroutineexceptionhandler-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%EB%A7%88%EC%A7%80%EB%A7%89-%EC%88%98%EB%8B%A8\" aria-label=\" 183 coroutineexceptionhandler 예외 처리를 위한 마지막 수단 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 18.3 CoroutineExceptionHandler: 예외 처리를 위한 마지막 수단</h2>\n<ul>\n<li>처리되지 않는 예외는 <code>CoroutineExceptionHandler</code>라는 특별한 핸들러에게 전달된다.</li>\n<li><code>CoroutineExceptionHandler</code>를 코루틴 콘텍스트에 제공하면 처리되지 않은 예외를 처리하는 동작을 커스텀화할 수 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main(): Unit = runBlocking {\n    val supervisor = ComponentWithScope()\n    supervisor.action()\n    delay(1.seconds)\n}\n\nclass ComponentWithScope(dispatcher: CoroutineDispatcher = Dispatchers.Default) {\n    private val exceptionHandler = CoroutineExceptionHandler { _, e -&gt;\n        println(&quot;[ERROR] ${e.message}&quot;)\n    }\n    private val scope = CoroutineScope(\n        SupervisorJob() + dispatcher + exceptionHandler\n    )\n\n    fun action() = scope.launch {\n        throw UnsupportedOperationException(&quot;Ouch!&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>커스텀 코루틴 예외 핸들러를 가진 컴포넌트</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">private val topLevelHandler = CoroutineExceptionHandler { _, e -&gt;\n    println(&quot;[TOP] ${e.message}&quot;)\n}\n\nprivate val intermediateHandler = CoroutineExceptionHandler { _, e -&gt;\n    println(&quot;[INTERMEDIATE] ${e.message}&quot;)\n}\n\n@OptIn(DelicateCoroutinesApi::class)\nfun main() {\n    GlobalScope.launch(topLevelHandler) {\n        launch(intermediateHandler) {\n            throw UnsupportedOperationException(&quot;Ouch!&quot;)\n        }\n    }\n    Thread.sleep(1000)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>코루틴 계층의 최상위에 있는 예외 핸들러만 호출된다.</li>\n</ul>\n<h3 id=\"-1831-coroutineexceptionhandler를-launch와-async에-적용할-때의-차이점\" style=\"position:relative;\"><a href=\"#-1831-coroutineexceptionhandler%EB%A5%BC-launch%EC%99%80-async%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%A0-%EB%95%8C%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90\" aria-label=\" 1831 coroutineexceptionhandler를 launch와 async에 적용할 때의 차이점 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.3.1 CoroutineExceptionHandler를 launch와 async에 적용할 때의 차이점</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class ComponentWithScope(dispatcher: CoroutineDispatcher = Dispatchers.Default) {\n    private val exceptionHandler = CoroutineExceptionHandler { _, e -&gt;\n        println(&quot;[ERROR] ${e.message}&quot;)\n    }\n    private val scope = CoroutineScope(\n        SupervisorJob() + dispatcher + exceptionHandler\n    )\n\n    fun action() = scope.launch {\n        async {\n            throw UnsupportedOperationException(&quot;Ouch!&quot;)\n        }\n    }\n}\n\nfun main() {\n    val supervisor = ComponentWithScope()\n    supervisor.action()\n    delay(1.seconds)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>슈퍼바이저의 직접적인 자식이면서 예외를 던지는 async 코루틴</li>\n<li>최상위 코루틴이 <code>async</code>로 시작되면 이 예외를 처리하는 책임은 <code>await()</code>를 호출하는 <code>Deferred</code> 소비자에게 있다.</li>\n</ul>\n<h2 id=\"-184-플로우에서-예외-처리\" style=\"position:relative;\"><a href=\"#-184-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%97%90%EC%84%9C-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC\" aria-label=\" 184 플로우에서 예외 처리 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 18.4 플로우에서 예외 처리</h2>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class UnhappyFlowException : Exception()\n\nval exceptionalFlow = flow {\n    repeat(5) { number -&gt;\n        emit(number)\n    }\n    throw UnhappyFlowException()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>5개의 숫자를 방출한 후 예외를 던지는 플로우</li>\n<li>일반적으로 플로우의 일부분에서 예외가 발생하면 <code>collect</code>에서 예외가 던져진다.</li>\n</ul>\n<h3 id=\"-1841-catch-연산자로-업스트림-예외-처리\" style=\"position:relative;\"><a href=\"#-1841-catch-%EC%97%B0%EC%82%B0%EC%9E%90%EB%A1%9C-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC\" aria-label=\" 1841 catch 연산자로 업스트림 예외 처리 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.4.1 catch 연산자로 업스트림 예외 처리</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    exceptionalFlow\n        .catch { cause -&gt;\n            println(&quot;\\nHandled: $cause&quot;)\n            emit(-1)\n        }\n        .collect {\n            println(&quot;$it &quot;)\n        }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>catch</code>는 플로우에서 발생한 예외를 처리할 수 있는 중간 연산자</li>\n<li>이 함수에 연결된 람다 안에서 플로우에 발생한 예외에 접근할 수 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    exceptionalFlow\n        .map { \n            it + 1\n        }\n        .catch { cause -&gt;\n            println(&quot;\\nHandled: $cause&quot;)\n        }\n        .onEach { \n            throw UnhappyFlowException()\n        }\n        .collect()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>catch</code>는 업스트림에 대해서만 작동하며, 플로우 처리 파이프라인의 앞쪽에서 발생한 예외들만 잡아낸다.</li>\n</ul>\n<h3 id=\"-1842-술어가-참일-때-플로우의-수집-재시도-retry-연산자\" style=\"position:relative;\"><a href=\"#-1842-%EC%88%A0%EC%96%B4%EA%B0%80-%EC%B0%B8%EC%9D%BC-%EB%95%8C-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EC%88%98%EC%A7%91-%EC%9E%AC%EC%8B%9C%EB%8F%84-retry-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1842 술어가 참일 때 플로우의 수집 재시도 retry 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.4.2 술어가 참일 때 플로우의 수집 재시도: retry 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val unreliableFlow = flow {\n    println(&quot;Starting the flow!&quot;)\n    repeat(10) { number -&gt;\n        if (Random.nextDouble() &lt; 0.1) throw CommunicationException()\n        emit(number)\n    }\n}\n\nfun main() = runBlocking {\n    unreliableFlow\n        .retry(5) { cause -&gt;\n            println(&quot;\\nHandled: $cause&quot;)\n            cause is CommunicationException\n        }\n        .collect { number -&gt;\n            println(&quot;$number&quot;)\n        }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>retry</code> 연산자는 업스트림의 예외를 잡는다.</li>\n<li>재시도할 때는 업스트림 연산자가 보두 다시 실행된다.</li>\n</ul>\n<h2 id=\"-185-코루틴과-플로우-테스트\" style=\"position:relative;\"><a href=\"#-185-%EC%BD%94%EB%A3%A8%ED%8B%B4%EA%B3%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8\" aria-label=\" 185 코루틴과 플로우 테스트 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 18.5 코루틴과 플로우 테스트</h2>\n<h3 id=\"-1851-코루틴을-사용하는-테스트를-빠르게-만들기-가상-시간과-테스트-디스패처\" style=\"position:relative;\"><a href=\"#-1851-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EB%B9%A0%EB%A5%B4%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EA%B0%80%EC%83%81-%EC%8B%9C%EA%B0%84%EA%B3%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%94%94%EC%8A%A4%ED%8C%A8%EC%B2%98\" aria-label=\" 1851 코루틴을 사용하는 테스트를 빠르게 만들기 가상 시간과 테스트 디스패처 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.5.1 코루틴을 사용하는 테스트를 빠르게 만들기: 가상 시간과 테스트 디스패처</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">class PlaygroundTest {\n\n    @Test\n    fun testDelay() = runTest {\n        val startTime = System.currentTimeMillis()\n        delay(20.seconds)\n        println(System.currentTimeMillis() - startTime)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>runTest</code>는 속도를 높이기 위해 특별한 테스트 디스패처와 스케줄러를 사용</li>\n<li><code>runTest</code>의 디스패처는 단일 스레드</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">@OptIn(ExperimentalCoroutinesApi::class)\n@Test\nfun testDelay() = runTest {\n    var x = 0\n    launch {\n        delay(500.milliseconds)\n        x++\n    }\n    launch {\n        delay(1.seconds)\n        x++\n    }\n    println(currentTime)\n\n    delay(600.milliseconds)\n    assertEquals(1, x)\n    println(currentTime)\n\n    delay(500.milliseconds)\n    assertEquals(2, x)\n    println(currentTime)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>delay</code>를 통해 가상 시계 진행</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">@OptIn(ExperimentalCoroutinesApi::class)\n@Test\nfun testDelay() = runTest {\n    var x = 0\n    launch { \n        x++\n        launch { \n            x++\n        }\n    }\n    launch { \n        delay(200.milliseconds)\n        x++\n    }\n    runCurrent()\n    assertEquals(2, x)\n    advanceUntilIdle()\n    assertEquals(3, x)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>미래의 어느 시점에 실행하도록 예약된 코루틴까지 실행하려면 <code>advanceUntilIdle</code> 함수를 사용할 수 있다.</li>\n</ul>\n<h3 id=\"-1852-터빈으로-플로우-테스트\" style=\"position:relative;\"><a href=\"#-1852-%ED%84%B0%EB%B9%88%EC%9C%BC%EB%A1%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%ED%85%8C%EC%8A%A4%ED%8A%B8\" aria-label=\" 1852 터빈으로 플로우 테스트 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 18.5.2 터빈으로 플로우 테스트</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val myFlow = flow { \n    emit(1)\n    emit(2)\n    emit(3)\n}\n\n@Test\nfun doTest() = runTest { \n    val results = myFlow.toList()\n    assertEquals(3, results.size)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>플로우를 리스트로 수집해서 확인할 수 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">@Test\nfun doTest() = runTest { \n    val results = myFlow.test {\n        assertEquals(1, awaitItem())\n        assertEquals(2, awaitItem())\n        assertEquals(3, awaitItem())\n        awaitComplete()\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>터빈은 서드파티 라이브러리지만 필수적이다.</li>\n<li>플로우가 방출한 모든 원소가 테스트에 의해 적절히 소비되도록 보장</li>\n</ul>","excerpt":"📖 18.1 코루틴 내부에서 던져진 오류 처리 일시 중단 함수나 코루틴 빌더 안에 작성한 코드도 예외를 발생시킬 수 있다. 코루틴 빌더는 실행할 새로운 코루틴을 생성하는데, 이 새로운 코루틴에서 발생한 예외는 catch 블록에 의해 잡히지 않는다 위 코드는 예외가 잡히지 않는다. launch에 전달되는 람다 블록안에서는 잡힌다. await를 감싼 try-catch에서 예외를 잡지만 동시에 예외를 출력한다. 자식 코루틴은 잡히지 않은 예외를 항상 부모 코루틴에 전파한다. 📖 18.…","fields":{"slug":"/backend/kotlin-in-action/18장-오류_처리와_테스트/"},"frontmatter":{"title":"Kotlin in Action - 18장 오류 처리와 테스트","thumbnail":{"childImageSharp":{"fluid":{"src":"/static/f7f7ddfa31d1614405dc5af1487b9ec4/9c94d/kotlin-in-action.png"}}},"draft":false,"category":"Back-End","tags":["Kotlin"],"date":"July 06, 2025"}},"node":{"id":"21a17e28-41fd-530f-90e8-1c0769e41d34","html":"<h2 id=\"-171-플로우-연산자로-플로우-조작\" style=\"position:relative;\"><a href=\"#-171-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%97%B0%EC%82%B0%EC%9E%90%EB%A1%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%A1%B0%EC%9E%91\" aria-label=\" 171 플로우 연산자로 플로우 조작 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.1 플로우 연산자로 플로우 조작</h2>\n<ul>\n<li>시퀀스와 마찬가지로 플로우도 중간 연산자와 최종 연산자를 구분</li>\n<li>중간 연산자는 코드를 실행하지 않고 변경된 플로우를 반환하며, 최종 연산자는 컬렉션, 개별 원소, 계산된 값을 반환하거나 아무 값도 반환하지 않으면서 플로우를 수집하고 실제 코드를 실행한다.</li>\n</ul>\n<h2 id=\"-172-중간-연산자는-업스트림-플로우에-적용되고-다운스트림-플로우를-반환한다\" style=\"position:relative;\"><a href=\"#-172-%EC%A4%91%EA%B0%84-%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%97%90-%EC%A0%81%EC%9A%A9%EB%90%98%EA%B3%A0-%EB%8B%A4%EC%9A%B4%EC%8A%A4%ED%8A%B8%EB%A6%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EB%B0%98%ED%99%98%ED%95%9C%EB%8B%A4\" aria-label=\" 172 중간 연산자는 업스트림 플로우에 적용되고 다운스트림 플로우를 반환한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.2 중간 연산자는 업스트림 플로우에 적용되고 다운스트림 플로우를 반환한다</h2>\n<ul>\n<li>중간 연산자는 플로우에 적용돼 새로운 플로우를 반환</li>\n<li>업스트림 플로우\n<ul>\n<li>연산자가 적용되는 플로우</li>\n</ul>\n</li>\n<li>다운스트림 플로우\n<ul>\n<li>중간 연산자가 반환하는 플로우</li>\n<li>또 다른 연산자의 업스트림 플로우로 작용할 수 있음.</li>\n</ul>\n</li>\n<li>시퀀스와 마찬가지로 중간 연산자가 호출되더라도 플로우 코드가 실제로 실행되지는 않는다.\n<ul>\n<li>콜드 상태</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1721-업스트림-원소별로-임의의-값을-배출-transform-함수\" style=\"position:relative;\"><a href=\"#-1721-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9B%90%EC%86%8C%EB%B3%84%EB%A1%9C-%EC%9E%84%EC%9D%98%EC%9D%98-%EA%B0%92%EC%9D%84-%EB%B0%B0%EC%B6%9C-transform-%ED%95%A8%EC%88%98\" aria-label=\" 1721 업스트림 원소별로 임의의 값을 배출 transform 함수 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.1 업스트림 원소별로 임의의 값을 배출: transform 함수</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val names = flow {\n        emit(&quot;Jo&quot;)\n        emit(&quot;May&quot;)\n        emit(&quot;Sue&quot;)\n    }\n    val uppercasedNames = names.map {\n        it.uppercase()\n    }\n    runBlocking {\n        uppercasedNames.collect { println(&quot;$it &quot;) }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>map</code> 함수는 업스트림 플로우를 받아 원소를 변환한 후 다운스트림 플로우에 원소를 배출 할 수 있다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val names = flow {\n        emit(&quot;Jo&quot;)\n        emit(&quot;May&quot;)\n        emit(&quot;Sue&quot;)\n    }\n    val upperAndLowercasedNames = names.transform {\n        emit(it.uppercase())\n        emit(it.lowercase())\n    }\n    runBlocking {\n        upperAndLowercasedNames.collect { println(&quot;$it &quot;) }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>하나 이상의 원소를 배출하고 싶을 때는 <code>transform</code> 함수 사용</li>\n<li><code>flowOf</code> 함수로 줄임표현 사용 가능</li>\n</ul>\n<h3 id=\"-1722-take나-관련-연산자는-플로우를-취소할-수-있다\" style=\"position:relative;\"><a href=\"#-1722-take%EB%82%98-%EA%B4%80%EB%A0%A8-%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%B7%A8%EC%86%8C%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4\" aria-label=\" 1722 take나 관련 연산자는 플로우를 취소할 수 있다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.2 take나 관련 연산자는 플로우를 취소할 수 있다</h3>\n<ul>\n<li><code>takeWhile</code> 같은 함수들을 플로우에서도 똑같이 쓸 수 있다.</li>\n<li>이런 연산자를 사용하면 연산자가 지정한 조건이 더 이상 유효하지 않을 때 업스트림 플로우가 취소되며, 더 이상 원소가 배출되지 않는다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val temps = getTemperatures()\n    temps\n        .take(5)\n        .collect {\n            log(it)\n        }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>take</code> 함수는 수집자와 관련된 코루틴 스코프를 취소하는 방식 외에 플로우 수집을 제어된 방식으로 취소하는 또 다른 방법</li>\n</ul>\n<h3 id=\"-1723-플로우의-각-단계-후킹-onstart-oneach-oncompletion-onempty\" style=\"position:relative;\"><a href=\"#-1723-%ED%94%8C%EB%A1%9C%EC%9A%B0%EC%9D%98-%EA%B0%81-%EB%8B%A8%EA%B3%84-%ED%9B%84%ED%82%B9-onstart-oneach-oncompletion-onempty\" aria-label=\" 1723 플로우의 각 단계 후킹 onstart oneach oncompletion onempty permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.3 플로우의 각 단계 후킹: onStart, onEach, onCompletion, onEmpty</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val temps = getTemperatures()\n    temps\n        .take(5)\n        .onCompletion { cause -&gt;\n            if (cause != null) {\n                println(&quot;An error occurred! $cause&quot;)\n            } else {\n                println(&quot;Completed!&quot;)\n            }\n        }\n        .collect {\n            println(it)\n        }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>원소 수집 후 종료되는 것을 확인하기 위해 <code>onCompletion</code> 연산자를 사용할 수 있다.\n<ul>\n<li>플로우가 정상 종료되거나, 취소되거나, 예외로 종료된 후에 호출되는 람다를 지정할 수 있게 해준다.</li>\n</ul>\n</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    flow\n        .onEmpty {\n            println(&quot;Nothing - emitting default value!&quot;)\n            emit(0)\n        }\n        .onStart {\n            println(&quot;Starting&quot;)\n        }\n        .onEach {\n            println(&quot;On $it&quot;)\n        }\n        .onCompletion {\n            println(&quot;Done!&quot;)\n        }\n        .collect()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>onCompletion</code>은 플로우 생명주기의 특정 단계에서 작업을 수행할 수 있는 중간 연산자</li>\n<li><code>onStart</code>는 플로우의 수집이 시작될 때 첫 번째 배출이 일어나기 전에 실행</li>\n<li><code>onEach</code>는 업스트림 플로우에서 배출된 각 원소에 대해 작업을 수행한 후 다운스트림 플로우에 전달</li>\n<li><code>onEmpty</code>는 로직을 추가로 수행하거나 기본값을 제공</li>\n</ul>\n<h3 id=\"-1724-다운스트림-연산자와-수집자를-위한-원소-버퍼링-buffer-연산자\" style=\"position:relative;\"><a href=\"#-1724-%EB%8B%A4%EC%9A%B4%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%97%B0%EC%82%B0%EC%9E%90%EC%99%80-%EC%88%98%EC%A7%91%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%90%EC%86%8C-%EB%B2%84%ED%8D%BC%EB%A7%81-buffer-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1724 다운스트림 연산자와 수집자를 위한 원소 버퍼링 buffer 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.4 다운스트림 연산자와 수집자를 위한 원소 버퍼링: buffer 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun getAllUserIds(): Flow&lt;Int&gt; {\n    return flow {\n        repeat(3) {\n            delay(200.milliseconds)\n            log(&quot;Emitting!&quot;)\n            emit(it)\n        }\n    }\n}\n\nsuspend fun getProfileFromNetwork(id: Int): String {\n    delay(2.seconds)\n    return &quot;Profile[$id]&quot;\n}\n\nfun main() {\n  val ids = getAllUserIds()\n  runBlocking {\n    ids\n      .map { getProfileFromNetwork(it) }\n      .collect { log(&quot;Got $it&quot;) }\n  }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>느린 데이터베이스에 접근해 사용자 식별자의 플로우를 얻어오는 과정</li>\n<li>값 생산자는 수집자가 이전 원소를 처리할 때까지 작업을 중단</li>\n</ul>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Emitting!\n2028 [main @coroutine#1] Got Profile[0]\n2234 [main @coroutine#1] Emitting!\n4240 [main @coroutine#1] Got Profile[1]\n4447 [main @coroutine#1] Emitting!\n6453 [main @coroutine#1] Got Profile[2]</code>\n        </deckgo-highlight-code>\n<ul>\n<li>ID의 배출과 프로필 요청이 뒤섞여 있다.</li>\n<li>원소가 배출되면 다운스트림 플로우가 해당 원소를 처리할 때까지 생산자 코드는 계속되지 않는다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    val ids = getAllUserIds()\n    runBlocking {\n        ids\n            .buffer(3)\n            .map { getProfileFromNetwork(it) }\n            .collect { log(&quot;Got $it&quot;) }\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#2] Emitting!\n213 [main @coroutine#2] Emitting!\n414 [main @coroutine#2] Emitting!\n2030 [main @coroutine#1] Got Profile[0]\n4040 [main @coroutine#1] Got Profile[1]\n6046 [main @coroutine#1] Got Profile[2]</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>buffer</code> 연산자는 버퍼를 추가해서 다운스트림 플로우가 이미 배출된 원소를 처리하느라 바쁜 동안에도 업스트림 플로우가 원소를 배출할 수 있게 해준다.</li>\n<li>버퍼를 추가했을 때 실행 시간이 줄어들었다.</li>\n<li><code>onBufferOverflow</code> 파라미터를 통해 버퍼 용량이 초과될 때 어떤 일이 발생할지 지정할 수 있다.\n<ul>\n<li>생산자 대기(SUSPEND)</li>\n<li>오래된 값 버리기(DROP_OLDEST)</li>\n<li>추가 중인 마지막 값을 버릴지(DROP_LATEST)</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"-1725-중간값을-버리는-연산자-conflate-연산자\" style=\"position:relative;\"><a href=\"#-1725-%EC%A4%91%EA%B0%84%EA%B0%92%EC%9D%84-%EB%B2%84%EB%A6%AC%EB%8A%94-%EC%97%B0%EC%82%B0%EC%9E%90-conflate-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1725 중간값을 버리는 연산자 conflate 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.5 중간값을 버리는 연산자: conflate 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    runBlocking {\n        val temps = getTemperatures()\n        temps\n            .onEach {\n                log(&quot;Read $it from sensor&quot;)\n            }\n            .conflate()\n            .collect { \n                log(&quot;Collected $it&quot;)\n                delay(1.seconds)\n            }\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">142709 [main @coroutine#1] Collected 6\n142864 [main @coroutine#2] Read 28 from sensor\n143370 [main @coroutine#2] Read 24 from sensor\n143711 [main @coroutine#1] Collected 24\n143876 [main @coroutine#2] Read -2 from sensor\n144382 [main @coroutine#2] Read 1 from sensor\n144715 [main @coroutine#1] Collected 1\n144888 [main @coroutine#2] Read 1 from sensor\n145391 [main @coroutine#2] Read 29 from sensor\n145719 [main @coroutine#1] Collected 29\n145893 [main @coroutine#2] Read 9 from sensor\n146396 [main @coroutine#2] Read 1 from sensor\n146725 [main @coroutine#1] Collected 1</code>\n        </deckgo-highlight-code>\n<ul>\n<li>값 생산자가 방해받지 않고 작업을 계속할 수 있게 하는 또 다른 방법은 수집자가 바쁜동안 배출된 항목을 그냥 버리는 것</li>\n<li>다운스트림의 수집자에서는 중간 원소가 버려진다.</li>\n<li><code>conflate</code>를 쓰면 업스트림 플로우의 실행을 다운스트림 연산자의 실행과 분리할 수 있다.</li>\n<li>느린 수집자가 플로우에서 최신 원소만 처리하게 함으로써 성능을 유지할 수 있다.</li>\n</ul>\n<h3 id=\"-1726-일정-시간-동안-값을-필터링하는-연산자-debounce-연산자\" style=\"position:relative;\"><a href=\"#-1726-%EC%9D%BC%EC%A0%95-%EC%8B%9C%EA%B0%84-%EB%8F%99%EC%95%88-%EA%B0%92%EC%9D%84-%ED%95%84%ED%84%B0%EB%A7%81%ED%95%98%EB%8A%94-%EC%97%B0%EC%82%B0%EC%9E%90-debounce-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1726 일정 시간 동안 값을 필터링하는 연산자 debounce 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.6 일정 시간 동안 값을 필터링하는 연산자: debounce 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">val searchQuery = flow {\n    emit(&quot;K&quot;)\n    delay(100.milliseconds)\n    emit(&quot;Ko&quot;)\n    delay(200.milliseconds)\n    emit(&quot;Kotl&quot;)\n    delay(500.milliseconds)\n    emit(&quot;Kotlin&quot;)\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>사용자가 검색 질의 문자열을 타이핑하는 것을 시뮬레이션</li>\n<li>일정시간동안 입력이 없으면 검색 결과 표시</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n  searchQuery\n    .debounce(250.milliseconds)\n    .collect {\n      log(&quot;Searching for $it&quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [main @coroutine#1] Searching for Kotl\n247 [main @coroutine#1] Searching for Kotlin</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>debounce</code> 연산자는 업스트림에서 원소가 배출되지 않은 상태로 정해진 타임아웃 시간이 지나야만 항목을 다운스트림 플로우로 배출</li>\n</ul>\n<h3 id=\"-1727-플로우가-실행되는-코루틴-콘텍스트를-바꾸기-flowon-연산자\" style=\"position:relative;\"><a href=\"#-1727-%ED%94%8C%EB%A1%9C%EC%9A%B0%EA%B0%80-%EC%8B%A4%ED%96%89%EB%90%98%EB%8A%94-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%BD%98%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EB%B0%94%EA%BE%B8%EA%B8%B0-flowon-%EC%97%B0%EC%82%B0%EC%9E%90\" aria-label=\" 1727 플로우가 실행되는 코루틴 콘텍스트를 바꾸기 flowon 연산자 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.2.7 플로우가 실행되는 코루틴 콘텍스트를 바꾸기: flowOn 연산자</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() {\n    runBlocking {\n        flowOf(1)\n            .onEach { log(&quot;A&quot;) }\n            .flowOn(Dispatchers.Default)\n            .onEach { log(&quot;B&quot;) }\n            .flowOn(Dispatchers.IO)\n            .onEach { log(&quot;C&quot;) }\n            .collect()\n    }\n}</code>\n        </deckgo-highlight-code>\n<deckgo-highlight-code language=\"text\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">0 [DefaultDispatcher-worker-3 @coroutine#3] A\n24 [DefaultDispatcher-worker-1 @coroutine#2] B\n26 [main @coroutine#1] C</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>flowOn</code> 연산자는 처리 파이프라인의 일부를 다른 디스패처나 다른 코루틴 콘텍스트에서 힐행할 수 있다.\n<ul>\n<li>코루틴 콘텍스트를 조정</li>\n</ul>\n</li>\n<li><code>flowOn</code> 연산자는 업스트림 플로우의 디스패처에만 영향을 미친다.</li>\n</ul>\n<h2 id=\"-173-커스텀-중간-연산자-만들기\" style=\"position:relative;\"><a href=\"#-173-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%A4%91%EA%B0%84-%EC%97%B0%EC%82%B0%EC%9E%90-%EB%A7%8C%EB%93%A4%EA%B8%B0\" aria-label=\" 173 커스텀 중간 연산자 만들기 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.3 커스텀 중간 연산자 만들기</h2>\n<ul>\n<li>일반적으로 중간 연산자는 동시에 수집자와 생산자의 역할을 한다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun Flow&lt;Double&gt;.averageOfLast(n: Int): Flow&lt;Double&gt; =\n    flow {\n        val numbers = mutableListOf&lt;Double&gt;()\n        collect { \n            if (numbers.size &gt; n) {\n                numbers.removeFirst()\n            }\n            numbers.add(it)\n            emit(numbers.average())\n        }\n    }\n\nfun main() = runBlocking {\n  flowOf(1.0, 2.0, 30.0, 121.0)\n    .averageOfLast(3)\n    .collect {\n      print(&quot;$it &quot;)\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>내부적으로 발생한 숫자의 리스트를 유지하면서 만난 값들의 평균을 배출할 수 있다.</li>\n<li>최적화는 밖에 드러나지 않고 코드의 행동방식에는 영향을 미치지 않는다.</li>\n</ul>\n<h2 id=\"-174-최종-연산자는-업스트림-플로우를-실행하고-값을-계산한다\" style=\"position:relative;\"><a href=\"#-174-%EC%B5%9C%EC%A2%85-%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%EC%97%85%EC%8A%A4%ED%8A%B8%EB%A6%BC-%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A5%BC-%EC%8B%A4%ED%96%89%ED%95%98%EA%B3%A0-%EA%B0%92%EC%9D%84-%EA%B3%84%EC%82%B0%ED%95%9C%EB%8B%A4\" aria-label=\" 174 최종 연산자는 업스트림 플로우를 실행하고 값을 계산한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>📖 17.4 최종 연산자는 업스트림 플로우를 실행하고 값을 계산한다</h2>\n<ul>\n<li>최종 연산자는 단일 값이나 값의 컬렉션을 계산하거나, 플로우의 실행을 촉발시켜 지정된 연산과 부수 효과를 수행한다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    getTemperatures()\n        .onEach { \n            log(it)\n        }\n        .collect()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>collect</code>는 플로우의 각 원소에 대해 실행할 람다를 지정할 수 있는 유용한 지름길을 제공</li>\n<li>최종 연산자는 업스트림 플로우의 실행을 담당하기 때문에 항상 일시 중단 함수다.</li>\n</ul>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">fun main() = runBlocking {\n    getTemperatures()\n        .first()\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li><code>first</code>나 <code>firstOrNull</code> 같은 최종연산자는 원소를 받은 다음에 업스트림 플로우를 취소할 수 있다.</li>\n</ul>\n<h3 id=\"-1741-프레임워크는-커스텀-연산자를-제공한다\" style=\"position:relative;\"><a href=\"#-1741-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EB%8A%94-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%97%B0%EC%82%B0%EC%9E%90%EB%A5%BC-%EC%A0%9C%EA%B3%B5%ED%95%9C%EB%8B%A4\" aria-label=\" 1741 프레임워크는 커스텀 연산자를 제공한다 permalink\" class=\"post-anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>🔖 17.4.1 프레임워크는 커스텀 연산자를 제공한다</h3>\n<deckgo-highlight-code language=\"kotlin\" terminal=\"carbon\" theme=\"one-dark\"  >\n          <code slot=\"code\">@Composable fun TemperatureDisplay(temps: Flow&lt;Int&gt;) {\n    val temperature = temps.collectAsState(null)\n    Box {\n        temperature.value?.let {\n            Text(&quot;The current temperature is $it!&quot;)\n        }\n    }\n}</code>\n        </deckgo-highlight-code>\n<ul>\n<li>텍스트를 상자 안에 넣는 간단한 코드</li>\n<li>플로우는 코루틴 기반 툴킷에 강력한 추가 기능을 제공</li>\n<li>코틀린 생태계의 어떤 프레임워크들은 플로우와 직접적인 통합을 제공하며, 커스텀 연산자와 변환 함수도 노출</li>\n</ul>","excerpt":"📖 17.1 플로우 연산자로 플로우 조작 시퀀스와 마찬가지로 플로우도 중간 연산자와 최종 연산자를 구분 중간 연산자는 코드를 실행하지 않고 변경된 플로우를 반환하며, 최종 연산자는 컬렉션, 개별 원소, 계산된 값을 반환하거나 아무 값도 반환하지 않으면서 플로우를 수집하고 실제 코드를 실행한다. 📖 17.…","fields":{"slug":"/backend/kotlin-in-action/17장-플로우_연산자/"},"frontmatter":{"title":"Kotlin in Action - 17장 플로우 연산자","thumbnail":{"childImageSharp":{"fluid":{"src":"/static/f7f7ddfa31d1614405dc5af1487b9ec4/9c94d/kotlin-in-action.png"}}},"draft":false,"category":"Back-End","tags":["Kotlin"],"date":"June 29, 2025"}}}},"staticQueryHashes":["2374173507","2996537568","3691437124"]}