https://kotlinlang.org logo
Title
x

xii

06/21/2022, 6:59 PM
hey! Is there any way when using
awaitAll()
with different variables of different types for it to not be casted as Any?
j

Joffrey

06/21/2022, 7:04 PM
Union types are not a thing in Kotlin yet, I guess that's what you're looking for?
x

xii

06/21/2022, 7:08 PM
perhaps that's it
basically i make 3 api calls and put them in a list
and wanted to await and do something like val (call1, call2) = awaitAll(call1Request, call2Request)
rather than just doing
val call1 = call1Request.await()
e

ephemient

06/21/2022, 7:17 PM
without heterogeneous lists and some fancy type operators, you can only specialize on specific sizes. for example,
suspend fun <A, B> Pair<Deferred<A>, Deferred<B>>.awaitAll(): Pair<A, B> = Pair(first.await(), second.await())
suspend fun <A, B, C> Triple<Deferred<A>, Deferred<B>, Deferred<C>>.awaitAll(): Triple<A, B, C> = Triple(first.await(), second.await(), third.await())
x

xii

06/21/2022, 7:19 PM
does three awaits like that do exactly the same that an await all would traditionally do on a list?
🇳🇴 1
three is good enough for me 😃
e

ephemient

06/21/2022, 7:22 PM
yes, it does
j

Joffrey

06/21/2022, 7:29 PM
@ephemient not really,
awaitAll
on a list of deferred fails on the earliest failure, regardless of order in the list. Using multiple await forces you to wait for all preceding tasks to finish before failing on the await that fails
x

xii

06/21/2022, 7:31 PM
that's unfortunate
e

ephemient

06/21/2022, 7:45 PM
oh that's true. in most cases you'll end up with similar behavior, if the deferred comes from async in the same coroutine scope, but not necessarily
perhaps you could get away with
suspend fun <A, B, C> Triple<Deferred<A>, Deferred<B>, Deferred<C>>.awaitAll(): Triple<A, B, C> = try {
    Triple(first.await(), second.await(), third.await())
} catch (e: Throwable) {
    first.cancel()
    second.cancel()
    third.cancel()
    throw e
}
or just cast away,
suspend fun <A, B, C> Triple<Deferred<A>, Deferred<B>, Deferred<C>>.awaitAll(): Triple<A, B, C> = listOf(first, second, third).awaitAll().let { (first, second, third) -> Triple(first as A, second as B, third as C) }
j

Joffrey

06/21/2022, 7:54 PM
Mmh I don't think the `catch`+
cancel
approach solves the problem because the time that you spend waiting before the failure is the problem, not the time after
But you're right that if those deferred come from the same scope with a regular (non supervisor) job, one failure should cancel the others anyway automatically
e

ephemient

06/21/2022, 7:55 PM
yeah the catch+cancel doesn't propagate in the same order, you'd need to drop down to a lower level with
invokeOnCompletion
to preserve it
but how much that matters, depends on the use
g

gildor

06/22/2022, 3:51 AM
Using multiple await forces you to wait for all preceding tasks to finish before failing on the await that fails
No, if you use own scope. This why I prefer (and use) this version:
suspend fun <A, B, C> awaitTriple(
        a: suspend () -> A,
        b: suspend () -> B,
        c: suspend () -> C,
): Triple<A, B, C> {
    return coroutineScope {
        val aDeferred = async { a() }
        val bDeferred = async { b() }
        val cDeferred = async { c() }
        Triple(
                aDeferred.await(),
                bDeferred.await(),
                cDeferred.await()
        )
    }
}
👍 1
This is what you usually want anyway, no need to create own scope and less chance to do mistake