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 AMrunBlockingCLOVIS
07/05/2023, 8:23 AMasync/await is non-blocking, like coroutinesCLOVIS
07/05/2023, 8:23 AMPromiseAdam 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 AMsuspendCLOVIS
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 AMasyncCLOVIS
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 asyncCLOVIS
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>