Skip to content

Commit c73fc0b

Browse files
committed
Use setTimeout-based dispatcher when process is not available on the target runtime
Fixes #1404
1 parent b37ca3a commit c73fc0b

File tree

2 files changed

+36
-11
lines changed

2 files changed

+36
-11
lines changed

kotlinx-coroutines-core/js/src/CoroutineContext.kt

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlin.coroutines.*
99

1010
private external val navigator: dynamic
1111
private const val UNDEFINED = "undefined"
12+
internal external val process: dynamic
1213

1314
internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
1415
// Check if we are running under ReactNative. We have to use NodeDispatcher under it.
@@ -24,6 +25,8 @@ internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when {
2425
// Check if we are in the browser and must use window.postMessage to avoid setTimeout throttling
2526
jsTypeOf(window) != UNDEFINED && window.asDynamic() != null && jsTypeOf(window.asDynamic().addEventListener) != UNDEFINED ->
2627
window.asCoroutineDispatcher()
28+
// If process is undefined (e.g. in NativeScript, #1404), use SetTimeout-based dispatcher
29+
jsTypeOf(process) == UNDEFINED -> SetTimeoutDispatcher
2730
// Fallback to NodeDispatcher when browser environment is not detected
2831
else -> NodeDispatcher
2932
}

kotlinx-coroutines-core/js/src/JSDispatcher.kt

+33-11
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,45 @@ package kotlinx.coroutines
77
import kotlinx.coroutines.internal.*
88
import org.w3c.dom.*
99
import kotlin.coroutines.*
10-
import kotlin.js.*
10+
import kotlin.js.Promise
1111

1212
private const val MAX_DELAY = Int.MAX_VALUE.toLong()
1313

1414
private fun delayToInt(timeMillis: Long): Int =
1515
timeMillis.coerceIn(0, MAX_DELAY).toInt()
1616

17-
internal object NodeDispatcher : CoroutineDispatcher(), Delay {
18-
override fun dispatch(context: CoroutineContext, block: Runnable) = NodeJsMessageQueue.enqueue(block)
17+
internal sealed class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay {
18+
override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
19+
val handle = setTimeout({ block.run() }, delayToInt(timeMillis))
20+
return ClearTimeout(handle)
21+
}
1922

2023
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
2124
val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis))
2225
// Actually on cancellation, but clearTimeout is idempotent
2326
continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler)
2427
}
28+
}
29+
30+
internal object NodeDispatcher : SetTimeoutBasedDispatcher() {
31+
override fun dispatch(context: CoroutineContext, block: Runnable) = NodeJsMessageQueue.enqueue(block)
32+
}
33+
34+
internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() {
35+
override fun dispatch(context: CoroutineContext, block: Runnable) = SetTimeoutMessageQueue.enqueue(block)
36+
}
2537

26-
private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle {
27-
override fun dispose() { clearTimeout(handle) }
28-
override fun invoke(cause: Throwable?) { dispose() }
29-
override fun toString(): String = "ClearTimeout[$handle]"
38+
private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle {
39+
40+
override fun dispose() {
41+
clearTimeout(handle)
3042
}
3143

32-
override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
33-
val handle = setTimeout({ block.run() }, delayToInt(timeMillis))
34-
return ClearTimeout(handle)
44+
override fun invoke(cause: Throwable?) {
45+
dispose()
3546
}
47+
48+
override fun toString(): String = "ClearTimeout[$handle]"
3649
}
3750

3851
internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay {
@@ -86,6 +99,16 @@ private object NodeJsMessageQueue : MessageQueue() {
8699
}
87100
}
88101

102+
private object SetTimeoutMessageQueue : MessageQueue() {
103+
override fun schedule() = scheduleProcess()
104+
105+
override fun reschedule() = scheduleProcess()
106+
107+
private fun scheduleProcess() {
108+
setTimeout({ process() }, 0)
109+
}
110+
}
111+
89112
/**
90113
* An abstraction over JS scheduling mechanism that leverages micro-batching of [dispatch] blocks without
91114
* paying the cost of JS callbacks scheduling on every dispatch.
@@ -136,4 +159,3 @@ internal abstract class MessageQueue : ArrayQueue<Runnable>() {
136159
// using them via "window" (which only works in browser)
137160
private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int
138161
private external fun clearTimeout(handle: Int = definedExternally)
139-
private external val process: dynamic

0 commit comments

Comments
 (0)