diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a807ec8ea..80996abcc47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ * `Colors`: * Added as a module. It should be used by default in case you wish all the API currently realized for `HEXAColor` +* `Coroutines`: + * Fix of [#374](https://github.com/InsanusMokrassar/MicroUtils/issues/374): + * Add vararg variants of `awaitFirst` + * Add `joinFirst` ## 0.21.2 diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AwaitFirst.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AwaitFirst.kt index 6f85dac8004..f7e6227115e 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AwaitFirst.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/AwaitFirst.kt @@ -3,6 +3,12 @@ package dev.inmo.micro_utils.coroutines import kotlinx.coroutines.* import kotlin.coroutines.* +/** + * Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all + * others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s + * + * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it + */ suspend fun Iterable>.awaitFirstWithDeferred( scope: CoroutineScope, cancelOnResult: Boolean = true @@ -24,10 +30,45 @@ suspend fun Iterable>.awaitFirstWithDeferred( } } +/** + * Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all + * others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s + * + * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it + */ suspend fun Iterable>.awaitFirst( scope: CoroutineScope, cancelOnResult: Boolean = true ): T = awaitFirstWithDeferred(scope, cancelOnResult).second + +/** + * Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all + * others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Deferred]s + * + * Creates [CoroutineScope] using [coroutineContext] for internal purposes + */ suspend fun Iterable>.awaitFirst( cancelOthers: Boolean = true ): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers) + +/** + * Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all + * others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [deferreds] + * + * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it + */ +suspend fun awaitFirst( + vararg deferreds: Deferred, + scope: CoroutineScope, + cancelOnResult: Boolean = true +): T = deferreds.toList().awaitFirstWithDeferred(scope, cancelOnResult).second +/** + * Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all + * others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [deferreds] + * + * Creates [CoroutineScope] using [coroutineContext] for internal purposes + */ +suspend fun awaitFirst( + vararg deferreds: Deferred, + cancelOthers: Boolean = true +): T = awaitFirst(*deferreds, scope = CoroutineScope(coroutineContext), cancelOnResult = cancelOthers) diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/JoinFirst.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/JoinFirst.kt new file mode 100644 index 00000000000..c4d036b63eb --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/JoinFirst.kt @@ -0,0 +1,64 @@ +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.* +import kotlin.coroutines.* + +/** + * Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins + * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s + * + * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it + */ +suspend fun Iterable.joinFirst( + scope: CoroutineScope, + cancelOthers: Boolean = true +): Job { + val resultDeferred = CompletableDeferred() + val scope = scope.LinkedSupervisorScope() + forEach { + scope.launch { + it.join() + resultDeferred.complete(it) + scope.cancel() + } + } + return resultDeferred.await().also { + if (cancelOthers) { + forEach { + runCatchingSafely { it.cancel() } + } + } + } +} +/** + * Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins + * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s + * + * Creates [CoroutineScope] using [coroutineContext] for internal purposes + */ +suspend fun Iterable.joinFirst( + cancelOthers: Boolean = true +): Job = joinFirst(CoroutineScope(coroutineContext), cancelOthers) + +/** + * Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins + * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s + * + * @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it + */ +suspend fun joinFirst( + vararg jobs: Job, + scope: CoroutineScope, + cancelOthers: Boolean = true +): Job = jobs.toList().joinFirst(scope, cancelOthers) + +/** + * Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins + * and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s + * + * Creates [CoroutineScope] using [coroutineContext] for internal purposes + */ +suspend fun joinFirst( + vararg jobs: Job, + cancelOthers: Boolean = true +): Job = joinFirst(*jobs, scope = CoroutineScope(coroutineContext), cancelOthers = cancelOthers)