2020-09-24 02:27:16 +00:00
|
|
|
package dev.inmo.micro_utils.coroutines
|
|
|
|
|
2020-12-22 17:17:28 +00:00
|
|
|
import kotlinx.coroutines.*
|
2020-12-22 09:16:06 +00:00
|
|
|
import kotlin.coroutines.CoroutineContext
|
|
|
|
import kotlin.coroutines.coroutineContext
|
2020-09-24 02:27:16 +00:00
|
|
|
|
|
|
|
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
2020-12-22 09:16:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
|
|
|
*/
|
|
|
|
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
|
|
|
|
2021-01-07 04:22:28 +00:00
|
|
|
/**
|
|
|
|
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
|
|
|
|
*/
|
|
|
|
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
|
|
|
try {
|
|
|
|
defaultSafelyExceptionHandler(it)
|
|
|
|
} catch (e: Throwable) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-22 17:17:28 +00:00
|
|
|
/**
|
|
|
|
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
|
|
|
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
|
|
|
*
|
|
|
|
* @see safelyWithContextExceptionHandler
|
|
|
|
* @see ContextSafelyExceptionHandler
|
|
|
|
*/
|
|
|
|
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
|
|
|
*
|
|
|
|
* @see safelyWithContextExceptionHandler
|
|
|
|
* @see ContextSafelyExceptionHandlerKey
|
|
|
|
*/
|
|
|
|
class ContextSafelyExceptionHandler(
|
|
|
|
val handler: ExceptionHandler<Unit>
|
|
|
|
) : CoroutineContext.Element {
|
|
|
|
override val key: CoroutineContext.Key<*>
|
|
|
|
get() = ContextSafelyExceptionHandlerKey
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
|
|
|
|
* exists
|
|
|
|
*
|
|
|
|
* @see ContextSafelyExceptionHandler
|
|
|
|
* @see ContextSafelyExceptionHandlerKey
|
|
|
|
*/
|
|
|
|
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
|
|
|
|
* already contains [ContextSafelyExceptionHandler], [ContextSafelyExceptionHandler.handler] will be used BEFORE
|
|
|
|
* [contextExceptionHandler] in case of exception.
|
|
|
|
*
|
|
|
|
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
|
|
|
|
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
|
|
|
|
*/
|
|
|
|
suspend fun <T> safelyWithContextExceptionHandler(
|
|
|
|
contextExceptionHandler: ExceptionHandler<Unit>,
|
|
|
|
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
|
|
|
block: suspend CoroutineScope.() -> T
|
|
|
|
): T {
|
|
|
|
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
|
|
|
|
ContextSafelyExceptionHandler {
|
|
|
|
oldHandler(it)
|
|
|
|
contextExceptionHandler(it)
|
|
|
|
}
|
|
|
|
} ?: ContextSafelyExceptionHandler(contextExceptionHandler)
|
|
|
|
return withContext(contextSafelyExceptionHandler) {
|
|
|
|
safely(safelyExceptionHandler, block)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 02:27:16 +00:00
|
|
|
/**
|
|
|
|
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
|
|
|
*
|
2020-12-22 09:21:25 +00:00
|
|
|
* Priorities of [ExceptionHandler]s:
|
|
|
|
*
|
|
|
|
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
|
|
|
|
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
|
|
|
* * [defaultSafelyExceptionHandler]
|
|
|
|
*
|
2021-05-24 06:27:54 +00:00
|
|
|
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
|
|
|
|
* be called [onException]
|
|
|
|
*
|
2020-09-24 02:27:16 +00:00
|
|
|
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
|
|
|
* exception will be available for catching
|
2020-12-22 09:16:06 +00:00
|
|
|
*
|
|
|
|
* @see defaultSafelyExceptionHandler
|
|
|
|
* @see safelyWithoutExceptions
|
2020-12-22 17:17:28 +00:00
|
|
|
* @see safelyWithContextExceptionHandler
|
2020-09-24 02:27:16 +00:00
|
|
|
*/
|
|
|
|
suspend inline fun <T> safely(
|
2020-12-22 16:45:43 +00:00
|
|
|
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
|
|
|
noinline block: suspend CoroutineScope.() -> T
|
2020-09-24 02:27:16 +00:00
|
|
|
): T {
|
|
|
|
return try {
|
2021-05-24 06:27:54 +00:00
|
|
|
withContext(SupervisorJob(), block)
|
2020-12-22 16:45:36 +00:00
|
|
|
} catch (e: Throwable) {
|
2020-12-22 17:17:28 +00:00
|
|
|
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
|
|
|
|
onException(e)
|
2020-09-24 02:27:16 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-02 15:25:33 +00:00
|
|
|
|
2021-05-24 18:18:46 +00:00
|
|
|
suspend inline fun <T> runCatchingSafely(
|
|
|
|
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
|
|
|
noinline block: suspend CoroutineScope.() -> T
|
|
|
|
): Result<T> = runCatching {
|
|
|
|
safely(onException, block)
|
|
|
|
}
|
|
|
|
|
2020-11-02 15:25:33 +00:00
|
|
|
/**
|
2021-05-24 06:27:54 +00:00
|
|
|
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
|
|
|
* returning null at one time
|
|
|
|
*
|
|
|
|
* @see safelyWithoutExceptions
|
|
|
|
* @see launchSafelyWithoutExceptions
|
|
|
|
* @see asyncSafelyWithoutExceptions
|
2021-01-07 04:22:28 +00:00
|
|
|
*/
|
2021-05-24 06:27:54 +00:00
|
|
|
val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
|
|
|
|
defaultSafelyWithoutExceptionHandler.invoke(it)
|
|
|
|
null
|
|
|
|
}
|
2021-01-07 04:22:28 +00:00
|
|
|
|
|
|
|
/**
|
2021-05-24 06:27:54 +00:00
|
|
|
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
|
|
|
* result from exception (instead of throwing it, by default always returns null)
|
2020-11-02 15:25:33 +00:00
|
|
|
*/
|
|
|
|
suspend inline fun <T> safelyWithoutExceptions(
|
2021-05-24 06:27:54 +00:00
|
|
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
2020-11-02 15:25:33 +00:00
|
|
|
noinline block: suspend CoroutineScope.() -> T
|
2021-05-24 06:27:54 +00:00
|
|
|
): T? = safely(onException, block)
|
2021-05-24 18:18:46 +00:00
|
|
|
|
|
|
|
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
|
|
|
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
|
|
|
noinline block: suspend CoroutineScope.() -> T
|
|
|
|
): Result<T?> = runCatching {
|
|
|
|
safelyWithoutExceptions(onException, block)
|
|
|
|
}
|