Hey all, so I'm trying to work around an inconsist...
# coroutines
b
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
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
Sweet, that looks really clean. Thanks.
n
Sounds like
select
would work?
Copy code
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
Yep, it's the lower level API I'm using in
raceOf
n
This was a fun puzzle and i spent way too long trying to come up with a solution.
Copy code
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:
Copy code
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
@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
oh nice! thanks for sending it over.
b
No worries. It's also like 20% faster than my initial polling version and it seems a lot safer too!
­čĹŹ 1
n
oh great!
l
@bsimmons Your snippet seems to be breaking structured concurrency as the exceptions would jump to an outer scope not local to the function.
b
Oh interesting. Which function is it? Do I need to catch
CancellationException
in
onAwait
?
l
You need to use
coroutineScope { }
so
async
calls use that scope.
b
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?)
Copy code
suspend fun foo() = coroutineScope { ... }
l
If they launch child coroutines via
launch
,
async
, or other means, yes.
b
Cool, the more you know. Thanks for all the pro tips.