improvements in coroutines launch safely

This commit is contained in:
InsanusMokrassar 2024-06-15 17:37:26 +06:00
parent 5e04521929
commit 5db4c5c717
2 changed files with 61 additions and 31 deletions

View File

@ -44,6 +44,24 @@ class ContextSafelyExceptionHandler(
get() = ContextSafelyExceptionHandlerKey get() = ContextSafelyExceptionHandlerKey
} }
suspend inline fun <T> runCatchingSafely(
onException: ExceptionHandler<T>,
block: suspend () -> T
): Result<T> {
return runCatching {
block()
}.onFailure {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it)
return runCatching {
onException(it)
}
}
}
suspend inline fun <T> runCatchingSafely(
block: suspend () -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
/** /**
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if * @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
* exists * exists
@ -64,7 +82,7 @@ suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExce
suspend fun <T> safelyWithContextExceptionHandler( suspend fun <T> safelyWithContextExceptionHandler(
contextExceptionHandler: ExceptionHandler<Unit>, contextExceptionHandler: ExceptionHandler<Unit>,
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler, safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T block: suspend () -> T
): T { ): T {
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler -> val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
ContextSafelyExceptionHandler { ContextSafelyExceptionHandler {
@ -96,24 +114,33 @@ suspend fun <T> safelyWithContextExceptionHandler(
* @see safelyWithoutExceptions * @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler * @see safelyWithContextExceptionHandler
*/ */
suspend fun <T> safely( suspend inline fun <T> safely(
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, onException: ExceptionHandler<T>,
block: suspend CoroutineScope.() -> T block: suspend () -> T
): T { ): T = runCatchingSafely(onException, block).getOrThrow()
return try {
supervisorScope(block)
} catch (e: Throwable) {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
onException(e)
}
}
suspend fun <T> runCatchingSafely( /**
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, * It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
block: suspend CoroutineScope.() -> T *
): Result<T> = runCatching { * Priorities of [ExceptionHandler]s:
safely(onException, block) *
} * * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [defaultSafelyExceptionHandler]
*
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
* be called [onException]
*
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
* exception will be available for catching
*
* @see defaultSafelyExceptionHandler
* @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler
*/
suspend inline fun <T> safely(
block: suspend () -> T
): T = safely(defaultSafelyExceptionHandler, block)
suspend fun <T, R> T.runCatchingSafely( suspend fun <T, R> T.runCatchingSafely(
onException: ExceptionHandler<R> = defaultSafelyExceptionHandler, onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
@ -122,8 +149,9 @@ suspend fun <T, R> T.runCatchingSafely(
safely(onException) { block() } safely(onException) { block() }
} }
@Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
suspend fun <T> safelyWithResult( suspend fun <T> safelyWithResult(
block: suspend CoroutineScope.() -> T block: suspend () -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
suspend fun <T, R> T.safelyWithResult( suspend fun <T, R> T.safelyWithResult(
@ -148,18 +176,20 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
* result from exception (instead of throwing it, by default always returns null) * result from exception (instead of throwing it, by default always returns null)
*/ */
suspend fun <T> safelyWithoutExceptions( suspend fun <T> safelyWithoutExceptions(
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T block: suspend () -> T
): T? = safely(onException, block) ): T? = runCatchingSafely(onException, block).getOrNull()
suspend fun <T> runCatchingSafelyWithoutExceptions( suspend fun <T> runCatchingSafelyWithoutExceptions(
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T?> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T block: suspend () -> T
): Result<T?> = runCatching { ): Result<T?> = runCatchingSafely(onException, block).let {
safelyWithoutExceptions(onException, block) if (it.isFailure) return Result.success<T?>(null)
it
} }
fun CoroutineScope( fun CoroutineScopeWithDefaultFallback(
context: CoroutineContext, context: CoroutineContext,
defaultExceptionsHandler: ExceptionHandler<Unit> defaultExceptionsHandler: ExceptionHandler<Unit>
) = CoroutineScope( ) = CoroutineScope(

View File

@ -10,7 +10,7 @@ fun CoroutineScope.launchSafely(
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> Unit block: suspend CoroutineScope.() -> Unit
) = launch(context, start) { ) = launch(context, start) {
safely(onException, block) runCatchingSafely(onException) { block() }
} }
fun CoroutineScope.launchSafelyWithoutExceptions( fun CoroutineScope.launchSafelyWithoutExceptions(
@ -19,7 +19,7 @@ fun CoroutineScope.launchSafelyWithoutExceptions(
onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> Unit block: suspend CoroutineScope.() -> Unit
) = launch(context, start) { ) = launch(context, start) {
safelyWithoutExceptions(onException, block) runCatchingSafelyWithoutExceptions(onException) { block() }
} }
fun <T> CoroutineScope.asyncSafely( fun <T> CoroutineScope.asyncSafely(
@ -28,7 +28,7 @@ fun <T> CoroutineScope.asyncSafely(
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) = async(context, start) { ) = async(context, start) {
safely(onException, block) runCatchingSafely(onException) { block() }
} }
fun <T> CoroutineScope.asyncSafelyWithoutExceptions( fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
@ -37,5 +37,5 @@ fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) = async(context, start) { ) = async(context, start) {
safelyWithoutExceptions(onException, block) runCatchingSafelyWithoutExceptions(onException) { block() }
} }