Kotlin/JS and Coroutines solve an issue that isn't really an issue?

Let me first state that I understand the use-cases for Coroutines in Android Development and the JVM. iOS I have no experience at all, so can’t say anything about it.

Another disclaimer: I’ve read quite a lot in the past months on Coroutines, but that doesn’t mean I fully understand it. That’s why I place it here for a discussion.

Some background:
I am working on a Kotlin multiplatform project with a JVM and a JS target. The common domain logic needs to be shared in the common module, just like some infrastructure. After going back and forth a bit with React wrappers and whatnot, I’ve settled with just ‘vanilla’ kotlinx.html which works pretty good!

I’ve started reading into Coroutines in the past months and visited a lot of talks at KotlinConf 2019. Nevertheless, I found the concept quite confusing at times, especially when looking at my usecase, where I want to target Kotlin/JS. However, I’ve started getting the feeling that coroutines are solving something that isn’t really an issue in JS anymore and make simple tasks pretty complicated…

1. The main usecase for Coroutines is asynchronous programming
This is also clearly stated in the Coroutines Overview:

Asynchronous or non-blocking programming is the new reality. Whether we’re creating server-side, desktop or mobile applications, it’s important that we provide an experience that is not only fluid from the user’s perspective, but scalable when needed.

As a bonus, coroutines not only open the doors to asynchronous programming, but also provide a wealth of other possibilities such as concurrency, actors, etc.

This focus on asynchronicity is also reflected in the documentation where every long running task is modelled with a delay. These are simulating IO-bound tasks.

However, there are also CPU-bound tasks, which hog a certain thread. These can’t be suspended at all, due to logical reasons, and the only way to solve it is to move them on a different thread.

2. JS doesn’t have multiple threads
JS only has 1 thread. Web workers can be used to create actual multithreaded pages, but they require launching a separate script. I don’t see how Coroutines could help here.

So, this means that coroutines in the JS world are useless if you want to use them for concurrency. Please note, that this isn’t Coroutines fault, but more the architecture of the web.

3. Callback hell is touted as problem to which coroutines are the answer…but really?
So, with threading not really possible, asynchronicity remains. In a lot of documents callback hell is mentioned as something that coroutines solve. Fair enough. But there were already a lot of solutions solving this, including Promises which allow chaining and things like Promise.all. It has been a long time since I’ve seen a lot of nested callbacks and this issue has been solved already. Sure, you need to write chains of then.

However, coroutines introduce a whole lot of constructs (scopes, contexts, launch, withContext, async, jobs, couroutineScope, classes implementing CouroutineScope, suspendCoroutine, callbackCoroutine ec etc) to solve an issue that, in JS IMHO, isn’t really an issue anymore for already a few years. And, when wrongfully using scopes and context and one of the various builders you can have performance that is worse off than just VanillaJS. Promises are almost impossible to do wrong.

Besides that you get a dichotomy between suspend functions and regular functions where the first one can only be called in a coroutine, whereas a function returning a Promise can be used everywhere and one can decide what to do with the result.

Conclusion
So, unless I am completely overlooking something, which is entirely possible… I have the feeling the coroutines are overly complicated and introduce a whole new range of possible misconfigurations for the JS world.

Ofcourse, you have channels and flows and what not, but these solve issues that are either not present in JS (multithreading) or already solved elsewhere (RxJS) and I don’t see any benefits of introducing a completely different library.

2 Likes

You are right that Kotlin’s coroutines introduce many quite complex features that are obsolete in JS due to its single-threaded nature.

But it is still worth using just for getting rid of the callback hell and promises hell in asynchronous programming. Instead of repeating arguments here I will asks you to read up on the async/await keywords and feature in the newest JS specifications. It provides the same solutions to callback hell.

A cool thing is that you can use Kotlin coroutines in very old browsers too. The browser does not need to natively support async/await yet. It does not even need to support Promises.

1 Like

Thanks for your response :slight_smile:

I am well aware of the async/await features in modern JS, which are easily retrofitted to older browser with babel.js, just like Promises. But I understand the benefit of targetting old browsers (IE) which don’t support Promises and async/await you propose. I did not yet consider that benefit with coroutines.

I wasn’t aware that you questioned the use of Kotlin at all. My response was assuming that you already decided to use Kotlin but unsure about whether to use coroutines.

If using Kotlin is a given, and if you want a async/await like feature, then there is no way around coroutines.

If using Kotlin is not decided yet, then there are whole bag of other arguments about the benefits of using it.

2 Likes

I wasn’t questioning the use of Kotlin at all and am indeed unsure about coroutines. So, indeed my argument about babel.js is not entirely fair.

For me Kotlin is the goto solution for cases where I have complicated domain logic, calculations and algorithms which I need to share between front and backend. There are a lot of solutions to to this, but Kotlin MPP is the least painful as far as I can see.

Following up on the complexity argument: I wholeheartedly agree that coroutines API is overly complicated for JS use cases. That’s why I always use the GlobalScope singleton in my Kotlin/JS trying to minimize the API burden.

1 Like

Ah, that is good to hear :slight_smile: Do you have some examples/articles somewhere or know them which follow a workable coroutine pattern in JS?

Unfortunately I don’t have any articles or official recommendations that talk about Kotlin/JS with coroutines, although I am constantly looking for new Kotlin articles to read. I would love to see some articles on this, too.

I can only tell what I know from my own experience working with Kotlin/JS and coroutines in a large-scale project (500 million page impressions per month).

If you do not have a very big single page application, it is totally sufficient to use GlobalScope. Our website is a mix between single page application and traditional pages (it is a set of many single page apps put together). Whenever it would be useful for the coroutine scope to be cancelled, our application is transitioning to a new webpage anyway. So the whole scope thing becomes moot in our case and using GlobalScope is ok in my opinion.

Also very useful is the coroutine API’s Deferred class. It is somewhat similar to Promises. Sometimes it is more convenient to return a Deferred than to make your function suspend. We use it for 2 use cases:

  • tasks/results that might or might not be needed to be run/used in future.
  • tasks that need to be cancelled and/or restarted with different arguments when a certain event occurs.

I am very satisfied with how well that all works in our project.

2 Likes

I’d be interested in hearing about the performance differences between Kotlin/JS coroutines and native JS async/await

One potential advantage of coroutines versus promises is in code readability. Suspension is a very natural concept. Suspension points let you easily indicate: “Let’s stop here, wait for something to happen, then pick up where we left off”; the kind of thing you need to do all the time in front-end. You can naturally create state scoped to the coroutine and be guaranteed nothing else has messed with it in between.

You can the same thing with Promises of course. Coroutines just provide syntactic sugar to keep track of computation state and to avoid unsightly .then chains.