diff --git a/CHANGELOG.md b/CHANGELOG.md index 672bb58192e..4183e078748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 0.4.19 +* `Coroutines`: + * New extension `Iterable#awaitFirstWithDeferred` has been added to identify which of `Deferred`s was + normally completed + * New extensions `Iterable>#invokeOnFirst` and `Iterable>.invokeFirstOf` have been + added + ## 0.4.18 * `Coroutines`: 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 c66a830c2b4..4c096027fd9 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,15 +3,15 @@ package dev.inmo.micro_utils.coroutines import kotlinx.coroutines.* import kotlin.coroutines.* -suspend fun Iterable>.awaitFirst( +suspend fun Iterable>.awaitFirstWithDeferred( scope: CoroutineScope, cancelOnResult: Boolean = true -): T = suspendCoroutine { continuation -> +): Pair, T> = suspendCoroutine, T>> { continuation -> scope.launch(SupervisorJob()) { val scope = this forEach { scope.launch { - continuation.resume(it.await()) + continuation.resume(it to it.await()) scope.cancel() } } @@ -27,6 +27,11 @@ suspend fun Iterable>.awaitFirst( } } } + +suspend fun Iterable>.awaitFirst( + scope: CoroutineScope, + cancelOnResult: Boolean = true +): T = awaitFirstWithDeferred(scope, cancelOnResult).second suspend fun Iterable>.awaitFirst( cancelOthers: Boolean = true ): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers) diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt new file mode 100644 index 00000000000..8bc71da51cb --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt @@ -0,0 +1,40 @@ +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.* + +class DeferredAction( + val deferred: Deferred, + val callback: suspend (T) -> O +) { + suspend operator fun invoke() = callback(deferred.await()) +} + +fun Deferred.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback) + +suspend fun Iterable>.invokeFirstOf( + scope: CoroutineScope, + cancelOnResult: Boolean = true +): O { + return map { it.deferred }.awaitFirstWithDeferred(scope, cancelOnResult).let { result -> + first { it.deferred == result.first }.invoke() + } +} + +suspend fun invokeFirstOf( + scope: CoroutineScope, + vararg variants: DeferredAction<*, O>, + cancelOnResult: Boolean = true +): O = variants.toList().invokeFirstOf(scope, cancelOnResult) + +suspend fun Iterable>.invokeOnFirst( + scope: CoroutineScope, + cancelOnResult: Boolean = true, + callback: suspend (T) -> O +): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult) + +suspend fun invokeOnFirst( + scope: CoroutineScope, + vararg variants: Deferred, + cancelOnResult: Boolean = true, + callback: suspend (T) -> O +): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback) diff --git a/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirstTests.kt b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirstTests.kt new file mode 100644 index 00000000000..22e611770ac --- /dev/null +++ b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirstTests.kt @@ -0,0 +1,27 @@ +package dev.inmo.micro_utils.coroutines + +import dev.inmo.micro_utils.coroutines.asDeferred +import dev.inmo.micro_utils.coroutines.launchSynchronously +import kotlinx.coroutines.* +import kotlin.test.* + +class DoWithFirstTests { + @Test + fun testHandleOneOf() { + val scope = CoroutineScope(Dispatchers.Default) + val happenedDeferreds = mutableListOf() + val deferredWhichMustHappen = (-1).asDeferred + scope.launchSynchronously { + scope.launch { + ((0 until 100).map { + DeferredAction( + scope.async { delay(10000); it }, + happenedDeferreds::add + ) + } + DeferredAction(deferredWhichMustHappen, happenedDeferreds::add)).invokeFirstOf(scope) + }.join() + } + assertEquals(1, happenedDeferreds.size) + assertEquals(scope.launchSynchronously { deferredWhichMustHappen.await() }, happenedDeferreds.first()) + } +}