Last active
September 27, 2023 07:28
-
-
Save vlas-ilya/6cf616ea05daad6833807d62c3ecb583 to your computer and use it in GitHub Desktop.
DSL for handling errors with launch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlinx.coroutines.* | |
object Dispatchers { | |
val Main: CoroutineDispatcher get() = kotlinx.coroutines.Dispatchers.Default | |
val IO: CoroutineDispatcher get() = kotlinx.coroutines.Dispatchers.IO | |
val Default: CoroutineDispatcher get() = kotlinx.coroutines.Dispatchers.Default | |
val Unconfined: CoroutineDispatcher get() = kotlinx.coroutines.Dispatchers.Unconfined | |
} | |
class LaunchBlock( | |
val coroutineScope: CoroutineScope, | |
val dispatcher: CoroutineDispatcher, | |
val safeAction: suspend () -> Unit | |
) | |
fun CoroutineScope.launchMain(block: suspend () -> Unit) = LaunchBlock(this, Dispatchers.Main, block) | |
fun CoroutineScope.launchIO(block: suspend () -> Unit) = LaunchBlock(this, Dispatchers.IO, block) | |
fun CoroutineScope.launchDefault(block: suspend () -> Unit) = LaunchBlock(this, Dispatchers.Default, block) | |
fun CoroutineScope.launchUnconfined(block: suspend () -> Unit) = LaunchBlock(this, Dispatchers.Unconfined, block) | |
fun CoroutineScope.launchSafe(dispatcher: CoroutineDispatcher, block: suspend () -> Unit) = | |
LaunchBlock(this, dispatcher, block) | |
infix fun LaunchBlock.and(launchBlock: LaunchBlock): MutableList<LaunchBlock> { | |
require(this.coroutineScope === launchBlock.coroutineScope) | |
return mutableListOf(this, launchBlock) | |
} | |
infix fun MutableList<LaunchBlock>.and(launchBlock: LaunchBlock): MutableList<LaunchBlock> { | |
require(this.isNotEmpty()) | |
require(this[0].coroutineScope === launchBlock.coroutineScope) | |
return this.apply { add(launchBlock) } | |
} | |
class ErrorBlock( | |
val dispatcher: CoroutineDispatcher, | |
val onError: suspend (Throwable) -> Unit, | |
) | |
fun on( | |
dispatcher: CoroutineDispatcher, | |
onError: suspend (Throwable) -> Unit, | |
) = ErrorBlock(dispatcher, onError) | |
infix fun LaunchBlock.catch(error: ErrorBlock): Job { | |
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> | |
coroutineScope.launch(error.dispatcher) { | |
error.onError(throwable) | |
} | |
} | |
return coroutineScope.launch(exceptionHandler + dispatcher) { | |
safeAction() | |
} | |
} | |
inline infix fun LaunchBlock.catch(crossinline onError: suspend (Throwable) -> Unit): Job { | |
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> | |
coroutineScope.launch(Dispatchers.Main) { | |
onError(throwable) | |
} | |
} | |
return coroutineScope.launch(exceptionHandler + dispatcher) { | |
safeAction() | |
} | |
} | |
inline infix fun List<LaunchBlock>.catch(crossinline onError: suspend (Throwable) -> Unit): List<Job> { | |
require(this.isNotEmpty()) | |
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> | |
this[0].coroutineScope.launch(Dispatchers.Main) { | |
onError(throwable) | |
} | |
} | |
val coroutineScope = CoroutineScope(Job()) | |
return this.map { | |
coroutineScope.launch(exceptionHandler + it.dispatcher) { | |
it.safeAction() | |
} | |
} | |
} | |
infix fun List<LaunchBlock>.catch(error: ErrorBlock): List<Job> { | |
require(this.isNotEmpty()) | |
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> | |
this[0].coroutineScope.launch(error.dispatcher) { | |
error.onError(throwable) | |
} | |
} | |
val coroutineScope = CoroutineScope(Job()) | |
return this.map { | |
coroutineScope.launch(exceptionHandler + it.dispatcher) { | |
it.safeAction() | |
} | |
} | |
} | |
// TESTS | |
suspend fun test() = with(CoroutineScope(SupervisorJob())) { | |
launchMain { | |
println("#0 launchMain runs, ${Thread.currentThread().name}") | |
delay(400) | |
error("#0 launchMain error, ${Thread.currentThread().name}") | |
} catch { | |
println("catch on(Dispatchers.Main) [$it], ${Thread.currentThread().name}") | |
} | |
launchIO { | |
println("#1 launchIO runs, ${Thread.currentThread().name}") | |
delay(400) | |
error("#1 launchIO error, ${Thread.currentThread().name}") | |
} and launchDefault { | |
println("#2 launchDefault runs, ${Thread.currentThread().name}") | |
delay(500) | |
error("#2 launchDefault error, ${Thread.currentThread().name}") | |
} and launchMain { | |
println("#3 launchMain runs, ${Thread.currentThread().name}") | |
delay(600) | |
error("#3 launchMain error, ${Thread.currentThread().name}") | |
} and launchUnconfined { | |
println("#4 launchUnconfined runs, ${Thread.currentThread().name}") | |
delay(700) | |
error("#4 launchUnconfined error, ${Thread.currentThread().name}") | |
} and launchSafe(Dispatchers.Unconfined) { | |
println("#5 launchSafe(Dispatchers.Unconfined) runs, ${Thread.currentThread().name}") | |
delay(800) | |
error("#5 launchSafe(Dispatchers.IO) error, ${Thread.currentThread().name}") | |
} catch on(Dispatchers.Default) { | |
println("catch on(Dispatchers.Default) [$it], ${Thread.currentThread().name}") | |
} | |
delay(1000) | |
} | |
fun main(): Unit = runBlocking { | |
println("before test") | |
test() | |
println("after test") | |
} | |
/* | |
OUTPUT: | |
before test | |
#0 launchMain runs, DefaultDispatcher-worker-2 | |
#1 launchIO runs, DefaultDispatcher-worker-1 | |
#4 launchUnconfined runs, main | |
#3 launchMain runs, DefaultDispatcher-worker-1 | |
#2 launchDefault runs, DefaultDispatcher-worker-3 | |
#5 launchSafe(Dispatchers.Unconfined) runs, main | |
catch on(Dispatchers.Main) [java.lang.IllegalStateException: #0 launchMain error, DefaultDispatcher-worker-1], DefaultDispatcher-worker-1 | |
catch on(Dispatchers.Default) [java.lang.IllegalStateException: #1 launchIO error, DefaultDispatcher-worker-3], DefaultDispatcher-worker-3 | |
after test | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment