
runBlocking function in Kotlin/JS. If you want to run your Kotlin code on the JS platform, you must forget about blocking threads. You can still use runTest for testing, which returns a promise in Kotlin/JS.Dispatchers.IO in Kotlin/JS because Dispatchers.IO is currently Kotlin/JVM-specific. However, I don't find this problematic: we should use Dispatchers.IO when we need to block threads, and that should only happen when we interoperate with JVM or native APIs.Continuation, which must be provided by Kotlin. We need to find a way to overcome this for other languages, and since Kotlin is a multiplatform language, these other languages might include all JVM languages (Java, Scala, Groovy, Clojure, ...), JavaScript languages (JavaScript, TypeScript), or native languages (Swift, C++, C, ...). Let's see the most important options that are available to us.class AnkiConnector( // ... ) { suspend fun checkConnection(): Boolean = ... suspend fun getDeckNames(): List<String> = ... suspend fun pushDeck( deckName: String, markdown: String ): AnkiConnectorResult = ... suspend fun pullDeck( deckName: String, currentMarkdown: String ): AnkiConnectorResult = ... }
runBlocking. This isn't the best solution, but it is the easiest.class AnkiConnectorBlocking { private val connector = AnkiConnector(/*...*/) fun checkConnection(): Boolean = runBlocking { connector.checkConnection() } fun getDeckNames(): List<String> = runBlocking { connector.getDeckNames() } fun pushDeck( deckName: String, markdown: String ): AnkiConnectorResult = runBlocking { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String ): AnkiConnectorResult = runBlocking { connector.pullDeck(deckName, currentMarkdown) } }
class AnkiConnector( // ... ) { suspend fun checkConnection(): Boolean = ... fun checkConnectionBlocking(): Boolean = runBlocking { connector.checkConnection() } // ... }
kotlin-jvm-blocking-bridge.class AnkiConnector( // ... ) { @JvmBlockingBridge suspend fun checkConnection(): Boolean = ... }
// Java class JavaTest { public static void main(String[] args) { AnkiConnector connector = new AnkiConnector(); boolean connection = connector.checkConnection(); // ... } }
Result, which represents a success when the wrapper function has finished, or it represents a failure in the case of an exception. I also made this callback function return Cancellable, which can be used to cancel the execution of a particular function.class AnkiConnectorCallback { private val connector = AnkiConnector(/*...*/) private val scope = CoroutineScope(SupervisorJob()) fun checkConnection( callback: (Result<Boolean>) -> Unit ): Cancellable = toCallback(callback) { connector.checkConnection() } fun getDeckNames( callback: (Result<List<String>>) -> Unit ): Cancellable = toCallback(callback) { connector.getDeckNames() } fun pushDeck( deckName: String, markdown: String, callback: (Result<AnkiConnectorResult>) -> Unit ): Cancellable = toCallback(callback) { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String, callback: (Result<AnkiConnectorResult>) -> Unit ): Cancellable = toCallback(callback) { connector.pullDeck(deckName, currentMarkdown) } fun <T> toCallback( callback: (Result<T>) -> Unit, body: suspend () -> T ): Cancellable { val job = scope.launch { try { val result = body() callback(Result.success(result)) } catch (t: Throwable) { callback(Result.failure(t)) } } return Cancellable(job) } class Cancellable(private val job: Job) { fun cancel() { job.cancel() } } }
Promise. To start a suspending function and expose its return value as a Promise which can be used in JavaScript to await this task, we can use the promise coroutine builder. Of course, this only works in Kotlin/JS.@JsExport @JsName("AnkiConnector") class AnkiConnectorJs { private val connector = AnkiConnector(/*...*/) private val scope = CoroutineScope(SupervisorJob()) fun checkConnection(): Promise<Boolean> = scope.promise { connector.checkConnection() } fun getDeckNames(): Promise<Array<String>> = scope.promise { connector.getDeckNames().toTypedArray() } fun pushDeck( deckName: String, markdown: String ): Promise<AnkiConnectorResult> = scope.promise { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String ): Promise<AnkiConnectorResult> = scope.promise { connector.pullDeck(deckName, currentMarkdown) } }
CompletableFuture from Java stdlib. For that, we should use the future coroutine builder.class AnkiConnectorFuture { private val connector = AnkiConnector(/*...*/) private val scope = CoroutineScope(SupervisorJob()) fun checkConnection(): CompletableFuture<Boolean> = scope.future { connector.checkConnection() } fun getDeckNames(): CompletableFuture<List<String>> = scope.future { connector.getDeckNames() } fun pushDeck( deckName: String, markdown: String ): CompletableFuture<AnkiConnectorResult> = scope.future { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String ): CompletableFuture<AnkiConnectorResult> = scope.future { connector.pullDeck(deckName, currentMarkdown) } }
Single or Observable, Kotlin provides a set of dependencies. For instance, to transform a suspending function into a Single from RxJava 3.x, we can use the kotlinx-coroutines-rx3 dependency and the rxSingle function.class AnkiConnectorBlocking { private val connector = AnkiConnector(/*...*/) fun checkConnection(): Single<Boolean> = rxSingle { connector.checkConnection() } fun getDeckNames(): Single<List<String>> = rxSingle { connector.getDeckNames() } fun pushDeck( deckName: String, markdown: String ): Single<AnkiConnectorResult> = rxSingle { connector.pushDeck(deckName, markdown) } fun pullDeck( deckName: String, currentMarkdown: String ): Single<AnkiConnectorResult> = rxSingle { connector.pullDeck(deckName, currentMarkdown) } }
runBlocking to construct a continuation in the other language, such as Java. This can be done in the following way.// Java public class MainJava { public static void main(String[] args) { AnkiConnector connector = new AnkiConnector(); boolean connected; try { connected = BuildersKt.runBlocking( EmptyCoroutineContext.INSTANCE, (s, c) -> connector.checkConnection(c) ); } catch (InterruptedException e) { throw new RuntimeException(e); } // ... } }
BuildersKtis maybe not the best name.- We need to specify some context, even though
EmptyCoroutineContextsuffices in our case. - We always need to deal with the
InterruptedExceptionthatrunBlockingthrows if the blocked thread is interrupted.
// Java class SuspendUtils { public static <T> T runBlocking( Function<Continuation<? super T>, T> func ) { try { return BuildersKt.runBlocking( EmptyCoroutineContext.INSTANCE, (s, c) -> func.apply(c) ); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public class MainJava { public static void main(String[] args) { AnkiConnector connector = new AnkiConnector(); boolean connected = SuspendUtils.runBlocking((c) -> connector.checkConnection(c) ); // ... } }
Flow and libraries representing reactive streams (like Reactor, RxJava 2.x. or RxJava 3.x).Flux, Flowable, or Observable implement the Publisher interface from the Java standard library, which can be converted to Flow with the asFlow function from the kotlinx-coroutines-reactive library.suspend fun main(): Unit = coroutineScope { Flux.range(1, 5).asFlow() .collect { print(it) } // 12345 Flowable.range(1, 5).asFlow() .collect { print(it) } // 12345 Observable.range(1, 5).asFlow() .collect { print(it) } // 12345 }
kotlinx-coroutines-reactor, you can convert Flow to Flux. With kotlinx-coroutines-rx3 (or kotlinx-coroutines-rx2), you can convert Flow to Flowable or Observable.suspend fun main(): Unit = coroutineScope { val flow = flowOf(1, 2, 3, 4, 5) flow.asFlux() .doOnNext { print(it) } // 12345 .subscribe() flow.asFlowable() .subscribe { print(it) } // 12345 flow.asObservable() .subscribe { print(it) } // 12345 }
// Kotlin object FlowUtils { private val scope = CoroutineScope(SupervisorJob()) @JvmStatic @JvmOverloads fun <T> observe( flow: Flow<T>, onNext: OnNext<T>? = null, onError: OnError? = null, onCompletion: OnCompletion? = null, ) { scope.launch { flow.onCompletion { onCompletion?.handle() } .onEach { onNext?.handle(it) } .catch { onError?.handle(it) } .collect() } } @JvmStatic @JvmOverloads fun <T> observeBlocking( flow: Flow<T>, onNext: OnNext<T>? = null, onError: OnError? = null, onCompletion: OnCompletion? = null, ) = runBlocking { flow.onCompletion { onCompletion?.handle() } .onEach { onNext?.handle(it) } .catch { onError?.handle(it) } .collect() } fun interface OnNext<T> { fun handle(value: T) } fun interface OnError { fun handle(value: Throwable) } fun interface OnCompletion { fun handle() } } class FlowTest { fun test(): Flow<String> = flow { emit("A") delay(1000) emit("B") } }
// Java public class Main { public static void main(String[] args) { FlowTest obj = new FlowTest(); FlowUtils.observeBlocking( obj.test(), System.out::println ); } } // A // (1 sec) // B
Flow into Promise or CompletableFuture. Flow can represent many values, but Promise or CompletableFuture can represent only one.[^403_2]: This is a simplified facade from my AnkiMarkdown project.