Extension methods for handling cooperative cancellation

The following extension methods are concise way to avoid having to write separate catch for cancellation exception over and over again.

/**
 * Executes a suspend function, catching all exceptions except [CancellationException].
 *
 * Does not catch [Error] or other [Throwable] types that don't extend [Exception].
 * Cancellation exceptions are always rethrown to preserve structured concurrency.
 *
 * Example:
 * ```
 * suspend {
 *   syncData()
 * }.catching { e ->
 *   logger.error("Sync failed", e)
 * }
 * ```
 */
suspend fun (suspend () -> Unit).catching(
  onError: suspend (Exception) -> Unit
) {
  try {
    this()
  } catch (e: CancellationException) {
    throw e
  } catch (e: Exception) {
    onError(e)
  }
}

/**
 * Executes a suspend function that returns a value, catching all exceptions except [CancellationException].
 *
 * Cancellation exceptions are always rethrown to preserve structured concurrency.
 *
 * @param onError Handler invoked when an exception occurs. Must return a fallback value of type [T].
 * @return The result of the block, or the fallback value from [onError] if an exception occurs.
 *
 * Example:
 * ```
 * val data = suspend {
 *   fetchUserData()
 * }.catchingReturns { e ->
 *   logger.error("Fetch failed", e)
 *   emptyList() // fallback value
 * }
 * ```
 */
suspend fun <T> (suspend () -> T).catchingReturns(
  onError: suspend (Exception) -> T
): T = try {
  this()
} catch (e: CancellationException) {
  throw e
} catch (e: Exception) {
  onError(e)
}

Backlinks