General question — When out refactoring some logic...
# coroutines
s
General question — When out refactoring some logic out of an existing suspend function which of these is preferred/best practice? If i'm starting with this and wanted to factor out the guts of `foo`:
Copy code
suspend fun doSomeWork(): Thing = supervisorScope {
  val foo = async {
    val result = async {
      fetchResultFromNetwork()
    }

    val otherResult = async {
      fetchOtherResultFromNetwork()
    }
    result.await() + otherResult.await()
  }
  
  val bar = async {
    anotherThing()
  }
  
  Thing(foo.await(), bar.await())
}
Would you go with: a) suspend fun + scope builder
Copy code
suspend fun doSomeWork(): Thing = supervisorScope {
  val foo = async {
    fetchAndMergeResults()
  }
  
  val bar = async {
    anotherThing()
  }
  
  Thing(foo.await(), bar.await())
}

suspend fun fetchAndMergeResults = coroutineScope {
  val result = async {
    fetchResultFromNetwork()
  }

  val otherResult = async {
    fetchOtherResultFromNetwork()
  }
  result.await() + otherResult.await()
}
b) Extension on CoroutineScope which returns a Deferred
Copy code
suspend fun doSomeWork(): Thing = supervisorScope {
  val foo = foo()
  
  val bar = async {
    anotherThing()
  }
  
  Thing(foo.await(), bar.await())
}

fun CoroutineScope.foo(): Deferred<Foo> {
  async {
    val result = async {
      fetchResultFromNetwork()
    }

    val otherResult = async {
      fetchOtherResultFromNetwork()
    }
    result.await() + otherResult.await()
  }
}
c) Something else? Any thoughts on what's preferred between the first two options? I'm aware of the conventions on suspend fun vs extension on CoroutineScope described in this video:

https://youtu.be/hQrFfwT1IMo?t=2431

and this blog post: https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055 and I think both the examples above follow the convention, but I'm not sure if one is preferred in this case for one reason or another. Any opinions would be appreciated!
g
My general rule always prefer suspend, never expose Deferred, so A
👍 1
s
That's what I was leaning towards. Out of curiosity — why do you generally prefer suspend and not expose Deferred?
g
Because it makes code look more natural, you don’t use CompletableFuture<T> if you can return just T, right? Also it makes code more flexbile, if some code wants to run some suspend function in parallel it can be done by just wrapping to
async {}
so no reason to provide deferred It’s in general easier to read code and less chance to forgot call .await()
👍 1
one more reason is that to start
async
you need scope, so you should somehow pass it (to keep structure concurrence). suspend function doesn’t require any scope, you can call it from any suspend lambda
s
That's true but by the time you have a suspend lambda you'll already be in a scope. Your points make a lot of sense though — the first option is easier to reason about and makes the function a little more broadly usable since the consumer can decide whether they want it to run concurrently with their code or not and the type signature is easier to reason about
Thanks for the input @gildor!
g
but by the time you have a suspend lambda you'll already be in a scope
But it's not true for usual suspend functions, only for coroutine builders Also it's not an issue of scope creation, but the fact that you always need additional argument for scope (or receiver), it becomes an issue if your want to use it for extension function