mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-12-18 14:47:15 +00:00
add subscribeAsync
This commit is contained in:
parent
fb9e4d57fb
commit
59fc90e556
@ -2,6 +2,10 @@
|
||||
|
||||
## 0.5.14
|
||||
|
||||
* `Coroutines`
|
||||
* `subscribeSafelyWithoutExceptions` got new parameter `onException` by analogue with `safelyWithoutExceptions`
|
||||
* New extensions `Flow#subscribeAsync` and subsequent analogs of `subscribe` with opportunity to set up custom marker
|
||||
|
||||
## 0.5.13
|
||||
|
||||
* `Common`:
|
||||
|
@ -4,6 +4,8 @@ package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
/**
|
||||
* Shortcut for chain if [Flow.onEach] and [Flow.launchIn]
|
||||
@ -29,9 +31,10 @@ inline fun <T> Flow<T>.subscribeSafely(
|
||||
*/
|
||||
inline fun <T> Flow<T>.subscribeSafelyWithoutExceptions(
|
||||
scope: CoroutineScope,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribe(scope) {
|
||||
safelyWithoutExceptions {
|
||||
safelyWithoutExceptions(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,118 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
private class SubscribeAsyncReceiver<T>(
|
||||
val scope: CoroutineScope,
|
||||
output: suspend SubscribeAsyncReceiver<T>.(T) -> Unit
|
||||
) {
|
||||
private val dataChannel: Channel<T> = Channel(Channel.UNLIMITED)
|
||||
val channel: SendChannel<T>
|
||||
get() = dataChannel
|
||||
|
||||
init {
|
||||
scope.launchSafelyWithoutExceptions {
|
||||
for (data in dataChannel) {
|
||||
output(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isEmpty(): Boolean = dataChannel.isEmpty
|
||||
}
|
||||
|
||||
private sealed interface AsyncSubscriptionCommand<T, M> {
|
||||
suspend operator fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>)
|
||||
}
|
||||
private data class AsyncSubscriptionCommandData<T, M>(
|
||||
val data: T,
|
||||
val scope: CoroutineScope,
|
||||
val markerFactory: suspend (T) -> M,
|
||||
val block: suspend (T) -> Unit,
|
||||
val onEmpty: suspend (M) -> Unit
|
||||
) : AsyncSubscriptionCommand<T, M> {
|
||||
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
|
||||
val marker = markerFactory(data)
|
||||
markersMap.getOrPut(marker) {
|
||||
SubscribeAsyncReceiver(scope) {
|
||||
safelyWithoutExceptions { block(it) }
|
||||
if (isEmpty()) {
|
||||
onEmpty(marker)
|
||||
}
|
||||
}
|
||||
}.channel.send(data)
|
||||
}
|
||||
}
|
||||
|
||||
private data class AsyncSubscriptionCommandClearReceiver<T, M>(
|
||||
val marker: M
|
||||
) : AsyncSubscriptionCommand<T, M> {
|
||||
override suspend fun invoke(markersMap: MutableMap<M, SubscribeAsyncReceiver<T>>) {
|
||||
val receiver = markersMap[marker]
|
||||
if (receiver ?.isEmpty() == true) {
|
||||
markersMap.remove(marker)
|
||||
receiver.scope.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, M> Flow<T>.subscribeAsync(
|
||||
scope: CoroutineScope,
|
||||
markerFactory: suspend (T) -> M,
|
||||
block: suspend (T) -> Unit
|
||||
): Job {
|
||||
val subscope = scope.LinkedSupervisorScope()
|
||||
val markersMap = mutableMapOf<M, SubscribeAsyncReceiver<T>>()
|
||||
val actor = subscope.actor<AsyncSubscriptionCommand<T, M>>(Channel.UNLIMITED) {
|
||||
it.invoke(markersMap)
|
||||
}
|
||||
|
||||
val job = subscribeSafelyWithoutExceptions(subscope) { data ->
|
||||
val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker ->
|
||||
actor.send(
|
||||
AsyncSubscriptionCommandClearReceiver(marker)
|
||||
)
|
||||
}
|
||||
actor.send(dataCommand)
|
||||
}
|
||||
|
||||
job.invokeOnCompletion { subscope.cancel() }
|
||||
|
||||
return job
|
||||
}
|
||||
|
||||
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
|
||||
scope: CoroutineScope,
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
|
||||
scope: CoroutineScope,
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safelyWithoutExceptions(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
|
||||
scope: CoroutineScope,
|
||||
noinline markerFactory: suspend (T) -> M,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribeAsync(scope, markerFactory) {
|
||||
safelyWithoutExceptions({ /* do nothing */}) {
|
||||
block(it)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user