Kotlin Coroutines and JavaScript
Kotlin can be compiled to JavaScript. This allows us to distribute our libraries or common code to use in JavaScript/TypeScript projects. However, their approach to concurrency is very different. JavaScript uses async functions and promises, while Kotlin uses suspending functions and flows. In this article we will explore how to make Kotlin Coroutines API JavaScript-friendly, and how to use JavaScript from Kotlin Coroutines.
Converting suspending functions to JavaScript async functions
In JavaScript functions that perform asynchronous operations are typically expected to return a Promise
. To convert a suspending function to a JavaScript async function, we can use promise
on a scope. This will start a new coroutine that will run asynchronously, and return a Promise
that resolves with the result of the coroutine.
The scope is typically provided by a dependency injection framework, but it can be something as simple as CoroutineScope(SupervisorJob())
. Such promises will run on Dispatchers.Default
, which in JavaScript uses one thread to run all coroutines. This is similar to how JavaScript works, as it uses a single thread to run all async functions.
If you need to convert suspending functions into callback-based API, I explained that in a separate article. In short, you can just start an asynchronous coroutine using launch
on a scope, and call the callback with the result of the suspending function. This is similar to how you would do it in JavaScript with async functions.
Converting Flow into JavaScript callback functions
We typically start and consume flow values using collect
method. However, it is a suspending function, and cannot be called from JavaScript directly. There are a couple of ways how we deal with that. One is to define a wrapper function, that can be used in JavaScript to consume our flow. This function can start a coroutine that collects values from the flow and calls a callback with each value.
For more convenience, we can instead define a wrapper class for a flow, that can be used in JavaScript directly to subscribe to the flow.
Here is a view model wrapper:
This is how FlowCallback
can be observed in React/TypeScript:
function useStateFlow<T>(
flow: FlowCallback<T>,
): T | undefined {
const [state, setState] = useState<T | undefined>(undefined);
useEffect(() => {
const job = flow.subscribe(scope, (value) => setState(value));
return () => job.cancel();
}, [flow]);
return state;
}
function NewsComponent({ viewModel }: { viewModel: NewsViewModelJs }) {
const newsState = useStateFlow(viewModel.state);
if (newsState === undefined) {
return <div>Loading...</div>;
}
if (newsState instanceof NewsState.Error) {
return <div>Error: {newsState.error.message}</div>;
}
return (
<ul>
{newsState.news.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
Calling JavaScript from Kotlin Coroutines
In JavaScript/TypeScript, we can generally meet four types of functions. The first one is a regular function that does not expect any callback or return any promise. Such functions can be called from Kotlin Coroutines without any problems, as they do not block the thread and do not require any special handling.
The second case is a function that returns a promise. Those are typically implemented as async function in JavaScript. Such functions can be handled like in JavaScript, using then
and catch
methods. However, in Kotlin Coroutines we can also use await
extension function to turn them into suspending functions. This allows us to call them from suspending functions without blocking the thread.
The third case is when a function expects a callback. I dedicated a separate article to explain how to turn callback functions into suspending functions or flows. In short, when we expect a callback to be called once, we should turn it into a suspending function using suspendCancellableCoroutine
. When we expect a callback to be called multiple times, we should turn it into a flow using callbackFlow
.
Conclusion
Kotlin Coroutines can be used with JavaScript, allowing us to write cancellable asynchronous code for many platforms, including web applications. We can convert suspending functions to JavaScript async functions using promise
, and we can convert callback-based APIs to suspending functions or flows using suspendCancellableCoroutine
and callbackFlow
. We can also convert JavaScript promises to suspending functions using await
, and we can call regular JavaScript functions directly from Kotlin Coroutines. This allows us to write Kotlin code that can be used in JavaScript/TypeScript projects, while still benefiting from the power of Kotlin Coroutines.
