Skip to content

Instantly share code, notes, and snippets.

@vlas-ilya
Last active September 27, 2023 07:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vlas-ilya/6cf616ea05daad6833807d62c3ecb583 to your computer and use it in GitHub Desktop.
Save vlas-ilya/6cf616ea05daad6833807d62c3ecb583 to your computer and use it in GitHub Desktop.
DSL for handling errors with launch
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