From 91339fc83926e8639b2bd3517eb1b4bcbd9d6f86 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 23 Sep 2025 13:37:59 +0600 Subject: [PATCH] rewrite firstof --- tgbotapi.core/api/tgbotapi.core.api | 3 +- .../kotlin/dev/inmo/tgbotapi/utils/FirstOf.kt | 63 ++++++++++++++----- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index eaa41b877a..2e8d592d53 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -32076,7 +32076,8 @@ public final class dev/inmo/tgbotapi/utils/ExtractDataAndJsonFromDecoderKt { } public final class dev/inmo/tgbotapi/utils/FirstOfKt { - public static final fun firstOf (Lkotlinx/coroutines/CoroutineScope;[Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun firstOf ([Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun firstOfOrNull ([Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class dev/inmo/tgbotapi/utils/IntProgress100Serializer : kotlinx/serialization/KSerializer { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/FirstOf.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/FirstOf.kt index 4863c34e7a..d16557afc9 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/FirstOf.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/FirstOf.kt @@ -1,26 +1,57 @@ package dev.inmo.tgbotapi.utils -import dev.inmo.micro_utils.coroutines.firstOf -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.merge +import kotlin.coroutines.cancellation.CancellationException /** - * Launches all provided suspending [deferreds] in this [CoroutineScope] and returns the value - * produced by the first block that completes. + * Executes the given suspend producers in parallel and returns the first successfully produced value, or null + * if none of them produce a value. * - * - Provide at least one block; otherwise the call will never complete. - * - Cancellation and error propagation semantics are delegated to the underlying - * dev.inmo.micro_utils.coroutines.firstOf implementation. + * Behaviour: + * - All [deferreds] are started concurrently. + * - Failures are ignored except [CancellationException], which is rethrown. + * - As soon as the first value is emitted, upstream flows are cancelled. * - * @param T The type of the resulting value. - * @param deferreds The suspending blocks to race; they are started eagerly. - * @return The result produced by the first completed block. + * Notes: + * - If every producer fails (without throwing [CancellationException]) or none emits a value, this returns null. + * + * @param T the type of the produced value + * @param deferreds suspend producers that are started in parallel + * @return the first successfully produced value, or null if none produced a value + * @throws CancellationException if the coroutine scope is cancelled or any producer throws it */ -suspend fun CoroutineScope.firstOf( +suspend fun firstOfOrNull( vararg deferreds: suspend () -> T -): T = firstOf { - deferreds.forEach { - add { - it() +): T? { + val resultFlow = deferreds.map { + flow { + runCatching { + it() + }.onSuccess { + emit(it) + }.onFailure { + if (it is CancellationException) throw it + } } - } + }.merge() + return resultFlow.firstOrNull() +} + +/** + * Executes the given suspend producers in parallel and returns the first successfully produced value. + * + * This is a non-nullable variant of [firstOfOrNull]. If no producer yields a value, it throws an [IllegalStateException]. + * + * @param T the type of the produced value + * @param deferreds suspend producers that are started in parallel + * @return the first successfully produced value + * @throws IllegalStateException if none of the producers yielded a value + * @throws CancellationException if the coroutine scope is cancelled or any producer throws it + */ +suspend fun firstOf( + vararg deferreds: suspend () -> T +): T { + return firstOfOrNull(*deferreds) ?: error("Unable to get result of deferreds") }