https://kotlinlang.org logo
Title
b

bsimmons

10/28/2020, 6:35 PM
Hey all, so I'm trying to work around an inconsistently slow API. What's the best way to make two async requests but only keep the fastest one to respond? (That infinite
while
seems like an antipattern to me.) Any ideas?
l

louiscad

10/28/2020, 6:40 PM
I made a
raceOf
function exactly for that in #splitties, the documentation is here: https://github.com/LouisCAD/Splitties/tree/main/modules/coroutines#racing-coroutines
b

bsimmons

10/28/2020, 6:46 PM
Sweet, that looks really clean. Thanks.
n

ntherning

10/29/2020, 7:21 AM
Sounds like
select
would work?
Select expression makes it possible to await multiple suspending functions simultaneously and select the first one that becomes available.
https://kotlinlang.org/docs/reference/coroutines/select-expression.html#selecting-deferred-values
1
l

louiscad

10/29/2020, 8:31 AM
Yep, it's the lower level API I'm using in
raceOf
n

Nick

10/29/2020, 1:36 PM
This was a fun puzzle and i spent way too long trying to come up with a solution.
flowOf(
    callbackFlow<String> {
        Timber.e("starting request 1")
        delay(1000)
        offer("Request 1 completed")
        invokeOnClose {
            Timber.e("Request 1 is now closed")
        }
    },
    callbackFlow<String> {
        Timber.e("starting request 2")
        delay(500)
        offer("Request 2 completed")
        invokeOnClose {
            Timber.e("Request 2 is now closed")
        }
    }
)
    .flattenMerge()
    .first()
    .also {
        Timber.e(it)
    }
Results are:
starting request 1
starting request 2
Request 2 is now closed
Request 2 completed
But I expected Request 1 to get closed at the end too. Do you guys think this is a bug?
b

bsimmons

10/29/2020, 2:06 PM
@ntherning Yes,
select
was the way to go to clean things up. @Nick Not sure what the problem you're having is, but here's the final solution I came up with if that helps. (It's a bit of a simplification of Louis' code.)
n

Nick

10/29/2020, 2:09 PM
oh nice! thanks for sending it over.
b

bsimmons

10/29/2020, 2:13 PM
No worries. It's also like 20% faster than my initial polling version and it seems a lot safer too!
👍 1
n

Nick

10/29/2020, 2:16 PM
oh great!
l

louiscad

10/29/2020, 2:33 PM
@bsimmons Your snippet seems to be breaking structured concurrency as the exceptions would jump to an outer scope not local to the function.
b

bsimmons

10/29/2020, 2:50 PM
Oh interesting. Which function is it? Do I need to catch
CancellationException
in
onAwait
?
l

louiscad

10/29/2020, 2:55 PM
You need to use
coroutineScope { }
so
async
calls use that scope.
b

bsimmons

10/29/2020, 3:09 PM
Oh, that's curious. I guess
async
by default uses a default/global scope? I never though about that. I guess in the general case, most suspend functions should be like this? (If they need to respect the current scope?)
suspend fun foo() = coroutineScope { ... }
l

louiscad

10/29/2020, 3:09 PM
If they launch child coroutines via
launch
,
async
, or other means, yes.
b

bsimmons

10/29/2020, 3:10 PM
Cool, the more you know. Thanks for all the pro tips.