kotlinbeginner

Suspend Functions — Basics and Patterns

Understand suspend functions: sequential vs concurrent, suspend composition, and callback wrapping.

kotlin
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

// Basic suspend function
suspend fun fetchUser(id: String): Map<String, Any> {
    delay(200) // simulate network
    return mapOf("id" to id, "name" to "User-$id", "email" to "$id@test.com")
}

suspend fun fetchOrders(userId: String): List<String> {
    delay(300) // simulate network
    return listOf("ORD-1", "ORD-2", "ORD-3")
}

suspend fun fetchBalance(userId: String): Double {
    delay(150)
    return 1234.56
}

// Sequential composition
suspend fun getUserProfile(id: String): Map<String, Any> {
    val user = fetchUser(id)  // waits 200ms
    val orders = fetchOrders(id)  // waits 300ms
    // Total: ~500ms (sequential)
    return user + mapOf("orders" to orders)
}

// Concurrent composition
suspend fun getUserProfileFast(id: String): Map<String, Any> = coroutineScope {
    val userDeferred = async { fetchUser(id) }      // starts immediately
    val ordersDeferred = async { fetchOrders(id) }   // starts immediately
    val balanceDeferred = async { fetchBalance(id) } // starts immediately
    // Total: ~300ms (concurrent, limited by slowest)

    val user = userDeferred.await()
    val orders = ordersDeferred.await()
    val balance = balanceDeferred.await()

    user + mapOf("orders" to orders, "balance" to balance)
}

// Wrapping callback to suspend
suspend fun fetchDataFromCallback(): String = suspendCancellableCoroutine { cont ->
    // Simulate callback-based API
    val callback = object {
        fun onSuccess(data: String) {
            cont.resume(data) {}
        }
        fun onError(error: Exception) {
            cont.resumeWithException(error)
        }
    }

    // Simulate async callback
    Thread {
        Thread.sleep(100)
        callback.onSuccess("callback-data")
    }.start()

    // Handle cancellation
    cont.invokeOnCancellation {
        println("Callback cancelled")
    }
}

// Suspend function as parameter
suspend fun <T> retry(times: Int, block: suspend () -> T): T {
    var lastException: Exception? = null
    repeat(times) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            lastException = e
            println("Attempt ${attempt + 1} failed: ${e.message}")
            delay(100 * (attempt + 1).toLong())
        }
    }
    throw lastException!!
}

// Suspend with timeout
suspend fun <T> fetchWithTimeout(timeoutMs: Long, block: suspend () -> T): T? =
    withTimeoutOrNull(timeoutMs) { block() }

fun main() = runBlocking {
    // Sequential
    val seqTime = measureTimeMillis {
        val profile = getUserProfile("user-1")
        println("Sequential: $profile")
    }
    println("Sequential time: ${seqTime}ms\n")

    // Concurrent
    val concTime = measureTimeMillis {
        val profile = getUserProfileFast("user-1")
        println("Concurrent: $profile")
    }
    println("Concurrent time: ${concTime}ms\n")

    // Callback wrapping
    val callbackResult = fetchDataFromCallback()
    println("Callback result: $callbackResult\n")

    // Retry
    var attempt = 0
    val retried = retry(3) {
        attempt++
        if (attempt < 3) throw RuntimeException("Not ready")
        "success"
    }
    println("Retried: $retried\n")

    // Timeout
    val fast = fetchWithTimeout(500) { fetchUser("1") }
    println("Fast (should succeed): $fast")

    val slow = fetchWithTimeout(50) {
        delay(200)
        "too slow"
    }
    println("Slow (should be null): $slow")

    // Collection of suspend calls
    println("\n--- Parallel fetch ---")
    val ids = listOf("1", "2", "3", "4", "5")
    val fetchTime = measureTimeMillis {
        val users = ids.map { id -> async { fetchUser(id) } }.awaitAll()
        println("Fetched ${users.size} users")
    }
    println("Parallel time: ${fetchTime}ms")
}

Use Cases

  • Sequential vs concurrent API calls
  • Callback-to-coroutine conversion
  • Retry and timeout patterns

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.