Good Code
The good version uses coroutineScope, so async work belongs to the suspending request and cancellation reaches both child jobs.
Lesson 06
Tie child coroutines to the request or screen that started the work.
suspend fun buildDigest(userId: UserId): Digest = coroutineScope {
// Child jobs cancel when the digest request is cancelled.
val user = async { users.find(userId) }
val reviews = async { reviewStore.recentFor(userId) }
Digest(user.await(), reviews.await())
}fun buildDigest(userId: UserId) {
// GlobalScope outlives the request that triggered the digest.
GlobalScope.launch {
val reviews = reviewStore.recentFor(userId)
mailer.sendDigest(userId, reviews)
}
}The good version uses coroutineScope, so async work belongs to the suspending request and cancellation reaches both child jobs.
The bad version launches work in GlobalScope, so a cancelled request can leave mail work running without the caller seeing failure.