Adam S
07/04/2023, 9:07 PMawait
, which is only possible inside async
functions. This is incongruous with JVM and Native, where it’s always possible to block the thread, and no async function
keyword is required.
So, I was thinking. would it be possible for a Kotlin compiler plugin to edit the Kotlin/JS compiler so it generates a async function
instead of a regular function wherever runBlocking {}
is used in a Kotlin/JS function?Adam S
07/04/2023, 9:12 PMfun main() {
println(getBlahs());
}
fun getBlahs(): String {
return "${blah()} and ${blah(3)}"
}
fun blah(): String = "blah"
fun blah(times: Int): String = runBlocking { "blah".repeat(times) }
would get compiled to JavaScript like this:
async function main() {
println(await getBlahs());
}
async function getBlahs() {
return blah() + ' and ' + (await blah_0(3));
}
function blah() {
return 'blah';
}
async function blah_0(times) { // 'async' is added to function definition
return Promise(repeat('blah', times));
}
Edoardo Luppi
07/04/2023, 9:20 PMrunBlocking
by using async/await it would have been done already.
I recall that this discussion for KotlinJS has been going on for quite a while, like years probablyEdoardo Luppi
07/04/2023, 9:23 PMEdoardo Luppi
07/04/2023, 9:29 PMAdam S
07/05/2023, 6:48 AMCLOVIS
07/05/2023, 8:22 AMrunBlocking
CLOVIS
07/05/2023, 8:23 AMasync/await
is non-blocking, like coroutinesCLOVIS
07/05/2023, 8:23 AMPromise
Adam S
07/05/2023, 8:26 AMThe concept of “blocking” doesn’t really exist in JSisn’t
await
effectively the same as runBlocking {}
?
What’s the difference between this JavaScript function
async function blah() {
const data = await foo()
return data
}
and this Kotlin function?
fun blah(): String {
val data = runBlocking { foo() }
return data
}
CLOVIS
07/05/2023, 8:27 AMawait
is the same as calling a suspend
function normally, or as calling KotlinX.coroutines' await()
functionCLOVIS
07/05/2023, 8:28 AMrunBlocking
means "I'm waiting for this function to end, but you are NOT allowed to do anything else. You're waiting with me."
On JS, that would make the entire tab unresponsive: no events could be sent anymore, so all buttons etc would do nothing.Adam S
07/05/2023, 8:29 AMCLOVIS
07/05/2023, 8:29 AMCLOVIS
07/05/2023, 8:30 AMrunBlocking
on the main thread on Android, except on JS there is nothing but the main thread, so it doesn't even compileAdam S
07/05/2023, 8:30 AMAdam S
07/05/2023, 8:32 AMrunBlocking { foo() }
to await foo()
then (assuming a little more magic 🪄 to create promises?) then it looks like it would work to meCLOVIS
07/05/2023, 8:35 AMwindow.alert("clicked")
When the website loads, you do
GlobalScope.launch {
delay(10_000)
window.alert("stopped")
}
If you click on the button, you get the alert immediately. 10 seconds later, the "stopped" alert appears.
Now, let's imagine runBlocking
existed. When the page loads, you do:
runBlocking {
delay(10_000)
window.alert("stopped")
}
You click on the button. Nothing happens. After 10 seconds, the "stopped" alter appears. When you close it, the "clicked" alert appears.
When you use runBlocking
, everything else in the page must wait for it to end before anything else can happen.Adam S
07/05/2023, 8:35 AMWhen you useyes, that’s fine by me, that’s what I want, everything else in the page must wait for it to end before anything else can happen.runBlocking
CLOVIS
07/05/2023, 8:36 AMCLOVIS
07/05/2023, 8:36 AMAdam S
07/05/2023, 8:36 AMAdam S
07/05/2023, 8:36 AMCLOVIS
07/05/2023, 8:36 AMMutex
then, but seriously your users don't want thatAdam S
07/05/2023, 8:37 AMCLOVIS
07/05/2023, 8:37 AMAdam S
07/05/2023, 8:38 AMsuspend
CLOVIS
07/05/2023, 8:40 AMCLOVIS
07/05/2023, 8:40 AMCLOVIS
07/05/2023, 8:41 AMAdam S
07/05/2023, 8:42 AMThe native APIs are callback/promise based, so you can’t use them in regular functionsyeah exactly, so I wanted to find out if there’s a way to tell Kotlin/JS to generate async functions
CLOVIS
07/05/2023, 8:43 AMawait
inside of themCLOVIS
07/05/2023, 8:43 AMAdam S
07/05/2023, 8:43 AMCLOVIS
07/05/2023, 8:44 AMasync
CLOVIS
07/05/2023, 8:45 AMAdam S
07/05/2023, 8:51 AMCLOVIS
07/05/2023, 8:53 AMasync
/return a promise. Regular functions in your Kotlin interface must be regular JS functions, so you can't call async
functions inside of them.Adam S
07/05/2023, 8:54 AMRegular functions in your Kotlin interface must be regular JS functionsbut what if a Kotlin compiler plugin added
async
to all generated JavaScript functions?CLOVIS
07/05/2023, 8:56 AMasync
JS function, so you would never be able to do any kind of interop with JSAdam S
07/05/2023, 8:59 AMasync
were added selectively, only where necessary?CLOVIS
07/05/2023, 8:59 AMAdam S
07/05/2023, 8:59 AM@JsAsync
annotationCLOVIS
07/05/2023, 9:00 AMAdam S
07/05/2023, 9:00 AMCLOVIS
07/05/2023, 9:03 AMinterface Foo {
fun foo(): String
]
class FooImpl1 {
override fun foo(): String = "1"
}
class FooImpl2 {
@JsAsync
override fun foo(): String = "2"
}
fun bar(impl: Foo): String {
return impl.foo()
}
How do you compile bar
? If the argument is FooImpl1
, it must be a regular JS function (not async
), but if the argument is FooImpl2
, then it must be async
.CLOVIS
07/05/2023, 9:04 AMsuspend
to overriden functions, so we just circled to the same thing)Adam S
07/05/2023, 9:05 AMAdam S
07/05/2023, 9:06 AMexpect class FooImpl
, which in jsMain would have the annotationsCLOVIS
07/05/2023, 9:08 AM@JsAsync
and add it to somehow. Which means whenever you add/remove JsAsync
to any function anywhere, your entire codebase changes signatureAdam S
07/05/2023, 9:08 AMAdam S
07/05/2023, 9:09 AMasync
to any usage of Foo.foo()
Adam S
07/05/2023, 9:12 AMasync
, but I’m not sure if a compiler plugin would be able to add async
to a function signature…CLOVIS
07/05/2023, 9:17 AMasync
are the functions you want, and all the functions that appear above on the call chain. If at any point you call
list.forEach {
yourAsyncFunction()
}
then List.forEach
must be async
, and therefore all functions that call it must be too. And now, everything has to be async
.Adam S
07/05/2023, 9:18 AMCLOVIS
07/05/2023, 9:19 AMAdam S
07/05/2023, 9:20 AMAdam S
07/05/2023, 9:21 AMforEach {}
doesn’t need to be made async, because I’d be using it inside one of my own library’s functionsCLOVIS
07/05/2023, 9:22 AMasync
too. All functions you call which accept lambdas must be async
if you call async
stuff inside of them, so forEach
, filter
, map
etc need to be async
as soon as a single of their usage calls async
in themCLOVIS
07/05/2023, 9:23 AMasync
, then all their usages must be async
too, etcCLOVIS
07/05/2023, 9:23 AMinline
functions (same way they don't need to suspend
), but that's only a small portion of functionsAdam S
07/05/2023, 9:28 AMfun operation(block: () -> Unit)
to fun operation(block: () -> Promise<Unit>)
Adam S
07/05/2023, 9:30 AMfun blah(data: List<String>) {
data.map { d ->
fs.foo(d) // async
}
}
then this would be translated to
async function blah(data) {
data.map { d ->
await fs.foo(d)
}
}
(or whatever the correct JS lambda signature equivalent is!)CLOVIS
07/05/2023, 9:32 AMmap
is inline
and all executions happen during the execution of the function. In all other cases, this code is wrong.CLOVIS
07/05/2023, 9:35 AMval tasks = ArrayList<() -> Unit>()
// imagine this is in the standard library
fun registerForLater(block: () -> Unit) {
tasks.add(block)
}
// this is also in the stdlib
fun executeAll() {
tasks.forEach { it() }
}
// this is your function
@JsAsync
fun blah(data: List<String>) {
data.forEach {
registerForLater { fs.foo(it) } ← await ?
}
executeAll() // ??
}
CLOVIS
07/05/2023, 9:37 AMsuspend
, even if blah
is suspend
, you can't call suspend
functions in the lambda of registerForLater
, for exactly the same reasonAdam S
07/05/2023, 9:39 AMAdam S
07/05/2023, 9:41 AMtasks
and registerForLater
to add async
CLOVIS
07/05/2023, 9:42 AMAdam S
07/05/2023, 9:43 AMAdam S
07/05/2023, 9:44 AMCLOVIS
07/05/2023, 9:45 AMCLOVIS
07/05/2023, 9:45 AMAdam S
07/05/2023, 9:46 AMPromise<Unit>