GettingNiceToHaveDataInAsyncWithoutCrashingRequiredDataGet

Pattern for getting nice to have data asynchronously without crashing the scope if nice to have data fails.

Code

package com.glassthought.sandbox

import com.glassthought.sandbox.util.out.impl.out
import gt.sandbox.util.output.Out
import kotlinx.coroutines.*

fun main() = runBlocking {
  initialize()
}

private suspend fun initialize() {
  try {
    // Use supervisor scope so that if async throws an exception prior to getting to await
    // we do not fail the entire scope.
    supervisorScope {
      val niceToHaveData = async(CoroutineName("queryNiceToHaveData")) {
        out.actionWithMsg("niceToHaveData", {
          throw FailureToGetNiceToHaveData("Failed to fetch niceToHaveData")
        })

        "nice-to-have-data-val"
      }

      val parseNotesDeferred = async(CoroutineName("required-data-2")) {
        out.actionWithMsg("required-data", {
          delay(100)
          "required-data-1-val"
        })
      }

      val niceToHaveResult = niceToHaveData.awaitOrNull(out)
      val requiredData1 = parseNotesDeferred.await()

      out.actionWithMsg("required-data-2", {
        out.info("niceToHaveDataResult:$niceToHaveResult")
        out.info("requiredDataResult:" + requiredData1)
      })
    }

  } catch (e: CancellationException) {
    out.cancelled("initialize", e)
  } catch (e: Throwable) {
    out.error("Initialize caught ERROR $e")
  }
}

private suspend fun <T> Deferred<T>.awaitOrNull(out: Out): T? {

  try {
    return this.await()
  } catch (e: CancellationException) {
    out.cancelled("awaitOrNull", e)
    throw e
  } catch (e: Exception) {
    out.warn("awaitOrNull caught [${e::class.simpleName}/${e.message}], returning null")
    return null
  }
}

class FailureToGetNiceToHaveData(msg: String) : RuntimeException(msg)

Command to reproduce:

gt.sandbox.checkout.commit 8b69f4ce79c906ff14c1 \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./gradlew run --quiet"

Recorded output of command:

Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
Picked up JAVA_TOOL_OPTIONS: -Dkotlinx.coroutines.debug
[INFO][elapsed:   28ms][①][coroutname:@queryNiceToHaveData#2] [->] action=[niceToHaveData] is starting.
[WARN][elapsed:   71ms][①][coroutname:@queryNiceToHaveData#2] [<-][💥] Finished action=[niceToHaveData], it THREW exception of type=[FailureToGetNiceToHaveData] we are rethrowing it.
[INFO][elapsed:   85ms][⓶][coroutname:@required-data-2#3] [->] action=[required-data] is starting.
[WARN][elapsed:   86ms][⓷][coroutname:@coroutine#1] awaitOrNull caught [FailureToGetNiceToHaveData/Failed to fetch niceToHaveData], returning null
[INFO][elapsed:  186ms][⓶][coroutname:@required-data-2#3] [<-] Finished action=[required-data].
[INFO][elapsed:  187ms][⓷][coroutname:@coroutine#1] [->] action=[required-data-2] is starting.
[INFO][elapsed:  187ms][⓷][coroutname:@coroutine#1]    niceToHaveDataResult:null
[INFO][elapsed:  187ms][⓷][coroutname:@coroutine#1]    requiredDataResult:required-data-1-val
[INFO][elapsed:  187ms][⓷][coroutname:@coroutine#1] [<-] Finished action=[required-data-2].

Backlinks