Exercise: raceOf
You implement an application that can take data from two sources and should always choose the faster response. For that, you decide to implement a raceOf
function that starts two or more asynchronous computations and returns the result of the first one that completes. This is an example of how it could work:
suspend fun fetchUserData(): UserData = raceOf(
{ service1.fetchUserData() },
{ service2.fetchUserData() }
)
Implement a raceOf
function that takes a list of suspending functions and returns the result of the first one that completes. Use the select
expression to implement it.
suspend fun <T> raceOf(
racer: suspend CoroutineScope.() -> T,
vararg racers: suspend CoroutineScope.() -> T
): T = TODO()
This problem can either be solved in the below playground or you can clone kotlin-exercises project and solve it locally. In the project, you can find code template for this exercise in coroutines/recipes/RaceOf.kt. You can find there starting code and unit tests.
Once you are done with the exercise, you can check your solution here.
import kotlinx.coroutines.*
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.test.currentTime
import kotlinx.coroutines.test.runTest
import kotlin.coroutines.CoroutineContext
import org.junit.Test
import kotlin.test.assertEquals
suspend fun <T> raceOf(
racer: suspend CoroutineScope.() -> T,
vararg racers: suspend CoroutineScope.() -> T
): T = TODO()
class RaceOfTest {
@Test
fun should_wait_for_the_fastest() = runTest {
raceOf(
{ delay(3) },
{ delay(1) },
{ delay(2) },
)
assertEquals(1, currentTime)
}
@Test
fun should_wait_for_the_fastest_for_big_number() = runTest {
val racers = List<suspend CoroutineScope.() -> Long>(1000) { i ->
{
val num = (i + 100).toLong()
delay(num)
num
}
}.shuffled().toMutableList()
val result = raceOf(racers.removeFirst(), *racers.toTypedArray())
assertEquals(100, result)
assertEquals(100, currentTime)
}
@Test
fun should_respond_with_fastest() = runTest {
val result = raceOf(
{ delay(3000); "C" },
{ delay(1000); "A" },
{ delay(2000); "B" },
)
assertEquals("A", result)
assertEquals(1000, currentTime)
}
@Test
fun should_cancel_slower() = runTest {
var slowerJob: Job? = null
val result = raceOf(
{ delay(1000); "A" },
{ slowerJob = currentCoroutineContext().job; delay(2000); "B" },
)
assertEquals("A", result)
assertEquals(1000, currentTime)
assertEquals(true, slowerJob?.isCancelled)
}
@Test
fun should_cancel_when_parent_cancelled() = runTest {
var innerJob: Job? = null
val job = launch {
raceOf(
{ delay(1000) },
{ innerJob = currentCoroutineContext().job; delay(2000) },
)
}
delay(500)
assertEquals(true, innerJob?.isActive)
job.cancel()
assertEquals(true, innerJob?.isCancelled)
}
@Test
fun should_propagate_context() = runTest {
var innerCtx: CoroutineContext? = null
val coroutineName1 = CoroutineName("ABC")
withContext(coroutineName1) {
raceOf(
{ delay(1000) },
{ innerCtx = currentCoroutineContext(); delay(2000) },
)
}
delay(500)
assertEquals(coroutineName1, innerCtx?.get(CoroutineName))
val coroutineName2 = CoroutineName("DEF")
withContext(coroutineName2) {
raceOf(
{ delay(1000) },
{ innerCtx = currentCoroutineContext(); delay(2000) },
)
}
delay(500)
assertEquals(coroutineName2, innerCtx?.get(CoroutineName))
}
}
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.