How do I call coroutines from JavaScript?

The subj.

class MyClass() {
    suspend myFunc() {}
}

In JS

const obj = new MyClass()
// obj.myFunc

Now how should I gracefully call obj.myFunc?

Calling a suspending function from native Javascript code will be very complicated. It will be easier and more JS-idiomatic to provide a second function that delegates to the first one, but returns a Promise:

fun MyClass.myFuncAsync(): Promise<Unit> = GlobalScope.promise {
    myFunc()
}

Then in your JS, you can call the new function and work with the promise:

const obj = new MyClass()
obj.myFuncAsync().then(function() {
    // code here is executed, when the coroutine finishes
});

Note, that this will work only in browsers, that already support Promises (older browsers don’t support Promises).

If you are on even newer browsers, you can use await keyword on the returned promise:

const obj = new MyClass()
await obj.myFuncAsync()
// code here is executed, when the coroutine finishes

If you need to support browsers that don’t support Promises at all yet, you will need to change the second function to accept a callback instead of returning a Promise:

fun MyClass.myFuncAsync(success: () -> Unit, error: () -> Unit) {
    GlobalScope.launch {
        try {
            myFunc()
            success()
        } catch (e: Throwable) {
            error()
        }
    }
}
4 Likes

Thank you, Sir.
But I have a multiplatfrom library and I cannot include Promises to the class. So, I am moving myFuncAsync to an extension like you show in the example. If it is an extension I cannot call it in JS as just:

const obj = new MyClass()
await obj.myFuncAsync()

since the extension does not fall into MyClass but being compiled as an external function:

  package$mypackage.myFuncAsync = myFuncAsync;

And this function does not contain any relations with the original object obj. So I can call neither obj.myFuncAsync nor myFuncAsync(obj)

Well, I change the call in another way:

fun myFuncPromise(obj: MyClass) = GlobalScope.promise {
    obj.myFunc()
}
const obj = new MyClass()
myFuncPromise(obj).then(() => console.log("WORKS?"))

This at last must work properly. But NO. This doesn’t (kotlin 1.3.21):

FAILURE TypeError: Cannot read property 'length' of undefined
    at createCoroutineUnintercepted (kotlin.js:25735)
    at startCoroutine (kotlin.js:34727)

Where kotlin.js:25735

    function createCoroutineUnintercepted($receiver, completion) {
      if ($receiver.length == 2) {
        return $receiver(completion, true);
      }
       else {
        var tmp$;
        return new createCoroutineFromSuspendFunction$ObjectLiteral(createCoroutineUnintercepted$lambda($receiver, completion), Kotlin.isType(tmp$ = completion, Continuation) ? tmp$ : throwCCE_0());
      }
    }

and $receiver is just a lambda.

And this is where I am stack

Oops, my fault. Of course you cannot call it as I have shown. You need to call it like this:

const obj = new package.x.y.z.MyClass()
package.x.y.z.myFuncAsync(obj).then(function() {
    // code here is executed, when the coroutine finishes
});

This should work. I can also not find a mistake in what you tried as an alternative. That should work also. Double check that your argument is not undefined.

1 Like

@fatjoe79 Thank you. Your latest suggestion works for me on a sample project. In my working code there is the above error, so I have to dig deeper.

My async function becomes:
package$shared.fetchWeatherAsync_kt2yc4$ = fetchWeatherAsync;

Then I have to call fetchWeatherAsync_kt2yc4$ from JS, which is not convenience at all.

Any suggestion is much appreciated!

Use @JsName annotation.

3 Likes

Hi every one,
Today i try this solution but i continue to have
i wrap my common function in the kotlin/js with promise like you show but i still have issue ?
some one have try it in his side ?