How do I wait for a suspending function to return ...
# getting-started
v
How do I wait for a suspending function to return a specific result? Something like
Copy code
for (i in 1..30) {
    if (suspendingFunction().contains("what I wait for")) {
        break
    }
    delay(1.seconds)
}
Just in nicer / fancier πŸ˜„
s
Is the fact that it's a suspending function important? The code you shared would work the same for any function
j
you could use repeat, but that's pretty much the best I could think of
your code is readable, that's enough πŸ™‚
πŸ‘ 1
v
That it is a suspending function is essential, yes. You think the code is readable, I might see it differently when seeing alternatives I didn't see. πŸ™‚ I tried something like
sequence { ... }
with
takeUntil
but that doesn't work exactly due to the suspending function and the
delay
too of course.
s
You could replace the sequence with a flow for a suspending alternative
nod 2
r
+1, suspending function that returns a different value each time it’s called really feels like it wants to be a flow
πŸ‘ 2
e
if you had a couple helpers like
Copy code
fun <T> Flow<T>.repeat(): Flow<T> = flow {
    while (true) emitAll(this@repeat)
}
fun <T> Flow<T>.afterEach(block: suspend (T) -> Unit): Flow<T> = transform {
    emit(it)
    block(it)
}
then you could write
Copy code
::suspendingFunction.asFlow()
    .repeat()
    .afterEach { delay(1.seconds) }
    .take(30)
    .firstOrNull { it.contains("what I wait for") }
r
That’s a very generic approach but I’d suggest you first think hard about what the suspend fun is doing and consider if it’s something you could make observable by default rather than requiring polling
v
Ah,
flow
is what I was missing, thanks, so
Copy code
flow {
    for (i in 1..30) {
        emit(suspendingFunction())
        delay(1.seconds)
    }
}.takeUnless { it.contains("what I wait for") }
πŸ™‚
but I’d suggest you first think hard about what the suspend fun is doing and consider if it’s something you could make observable by default rather than requiring polling
It calls a utility and waits for the utility to not say anymore "upgrade in progress"
e
that does have a slight side-effect of waiting for a second after the 30th fail
v
Usually it should not get to the 2nd from my tests. πŸ˜„ I risk that second for not having to write
Copy code
flow {
    for (i in 1 until 30) {
        emit(suspendingFunction())
        delay(1.seconds)
    }
    emit(suspendingFunction())
}.takeUnless { it.contains("what I wait for") }
πŸ˜„
e
Copy code
flow {
    while (true) {
        emit(suspendingFunction())
        delay(1.seconds)
    }
}.take(30)
doesn't have the trailing delay (and is equivalent to what I wrote earlier with helper functions)
v
Btw. your version up there, wouldn't it continuously repeat the same value? Or did I misinterpret the docs of
asFlow()
e
it is defined as
Copy code
fun <T> (suspend () -> T).asFlow(): Flow<T> = flow {
    emit(invoke())
}
which is kind of the only thing that could make sense. it can't call the function outside the flow since it's not itself
suspend
v
hm, yeah, right, thx. As I right now only need it in one place, I guess I'm going with
Copy code
flow {
    while (true) {
        emit(suspendingFunction())
        delay(1.seconds)
    }
}
    .take(30)
    .takeUnless { it.contains("what I wait for") }
Thanks all
Well, maybe I should add a
.collect()
or it's not doing anything πŸ˜„
e
or any other terminal operation, hence why I wrote
.firstOrNull
πŸ‘Œ 1
v
Ah, yes, better indeed, thanks:
Copy code
flow {
    while (true) {
        emit(wslStatus())
        delay(1.seconds)
    }
}
    .take(30)
    .firstOrNull { !it.contains("WSL is finishing an upgrade...") }
j
You could also write:
Copy code
flowOf(1..30)
    .transform {
        emit(wslStatus())
        delay(1.seconds)
    }
    .firstOrNull { !it.contains("WSL is finishing an upgrade...") }
v
oh, that's even nicer, no endless loop, thanks πŸ™‚
πŸ‘ 1
e
well it has the wait-at-end issue again but maybe it's not important πŸ˜›
☝️ 1
j
this could be fixed by adding a
take
I think?
v
Hm, and would it work? IDE says
it
in
transform
is
IntRange
πŸ€” 1
j
that's not what I see
v
It is exactly what you see
Second line, end of line
e
slightly uglier but hypothetically you could swap things around a little
Copy code
(2..30).asFlow().onEach { delay(1.seconds) }.onStart { emit(1) }.map { wslStatus() }.firstOrNull { ... }
j
Second line, end of line
Oh right! That's not an issue, it's just the range we use on the first line, but it's overridden by the
emit
in the third line
v
Oh right! That's not an issue, it's just the range we use on the first line, but it's overridden by the
emit
in the third line
But wouldn't the flow only be of size 1 then?
e
yes, you want
.asFlow()
, not
flowOf()
j
oh right!
I missed that πŸ˜…
v
slightly uglier but hypothetically you could swap things around a little
@ephemient but that would do the delay before each value is emitted, so adds an unnecessary 1 second wait before the first invocation, wouldn't it?
e
no, the
onStart
adds an element before any of the delays
v
Ah, right, I should read the whole flow and open my eyes, sorry.
e
to me the infinite loop version is fine though :)
v
Thanks again, I think indeed I like
Copy code
(2..30)
    .asFlow()
    .onEach { delay(1.seconds) }
    .onStart { emit(1) }
    .map { wslStatus() }
    .firstOrNull { !it.contains("WSL is finishing an upgrade...") }
best while not introducing extensions. Or when not caring about the final delay that should not be reached anyway
Copy code
(1..30)
    .asFlow()
    .transform {
        emit(wslStatus())
        delay(1.seconds)
    }
    .firstOrNull { !it.contains("WSL is finishing an upgrade...") }
Indent nicer than the endless loop version πŸ™‚