2

I'm trying to learn the right way to use Kotlin coroutines to do some parallel API calls. I've been able to get the expected results/process, but am wondering if there is a "more correct" way to do so.

Here's my contrived example of what I'm trying to do. Use case is essentially an inbound web request, and that request in turn needing to make a couple of API calls. Digging around led me to using launch or async:

// using launch
var recordExists = false
var otherRecordExists = false

coroutineScope {
    launch {
        recordExists = someHttpService.doesRecordExist(123)
    }
    launch {
        otherRecordExists = someHttpService.doesRecordExist(456)
    }
}

if (!recordExists || !otherRecordExists) {...}

vs

// using async
var recordExists = false
var otherRecordExists = false

coroutineScope {
    val recordDeferred = async { someHttpService.doesRecordExist(123) }
    val otherRecordDeferred = async { someHttpService.doesRecordExist(456) }

    recordExists = recordDeferred.await()
    otherRecordExists = otherRecordDeferred.await()
}

if (!recordExists || !otherRecordExists) {...}

I landed on using launch because it felt cleaner, but not sure if that was the most appropriate decision reasoning...

Is there a specific reason to use one over the other that I'm overlooking?

Tap
  • 113
  • 2
  • 5

1 Answers1

4

Your code is okay but as a general practice, I would recommend avoiding using var as much as possible when dealing with concurrency and parallelism. Having a shared mutable state is very risky and can often lead to race-conditions. In your case you can do something like:

coroutineScope {
    val recordDeferred = async { someHttpService.doesRecordExist(123) }
    val otherRecordDeferred = async { someHttpService.doesRecordExist(456) }

    val recordExists = recordDeferred.await()
    val otherRecordExists = otherRecordDeferred.await()

    if (!recordExists || !otherRecordExists) {
        ...
    }
}

Here you can only use async as you need to get some data back from the coroutine.

Arpit Shukla
  • 9,612
  • 1
  • 14
  • 40
  • 1
    Definitely this. Don't have the vars at all; keep everything in the same coroutine scope. (Consider even skipping the `recordExists` variables, and just write `if (!recordExists.await()...)`. – Louis Wasserman Oct 11 '22 at 22:46
  • Yes! I kind of hated that I felt like I "had" to use var's in the code above. We ended up doing something more like: `val someVar = coroutineScope { (...code that then returns on last line) }` We needed `someVar` to then be returned from the method (and wasn't sure if returning directly within the coroutineScope to the outer method was bad practice?) – Tap Nov 30 '22 at 14:12