{"componentChunkName":"component---src-containers-post-index-tsx","path":"/backend/kotlin-in-action/18장-오류_처리와_테스트/","result":{"pageContext":{"next":{"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"}},"previous":null,"node":{"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"}}}},"staticQueryHashes":["2374173507","2996537568","3691437124"]}