Web Workers¶
WebWorker runs a self-contained JavaScript job off the main thread on the web targets. It exists on Kotlin/JS and Kotlin/Wasm with an identical API; the other targets have real threads and use ioDispatcher instead.
| Target | WebWorker, runInWorker |
|---|---|
| js | available (declared in jsMain) |
| wasmJs | available (declared in wasmJsMain) |
| JVM, Android, Apple | not present; use ioDispatcher |
The worker is created at runtime from a Blob URL, so there is no separate script file to bundle. The job runs in the Worker scope with no access to your Kotlin module: everything it needs must arrive through the payload string.
Run a one-shot job¶
runInWorker(jobJs, payload) builds a worker, runs the job once, and closes the worker:
import io.github.yuroyami.kitecore.runInWorker
suspend fun sha256Hex(text: String): String = runInWorker(
jobJs = """async (payload) => {
const bytes = new TextEncoder().encode(payload)
const digest = await crypto.subtle.digest('SHA-256', bytes)
return Array.from(new Uint8Array(digest))
.map(b => b.toString(16).padStart(2, '0')).join('')
}""",
payload = text,
)
jobJs is a JavaScript function expression (payload) => result, synchronous or asynchronous. The worker awaits the result and normalizes it to a string before replying.
Reuse a worker across calls¶
Creating a worker costs a script compile and a thread spawn. For repeated jobs, build one with WebWorker.of and keep it:
import io.github.yuroyami.kitecore.WebWorker
val primeWorker = WebWorker.of(
"""(payload) => {
const n = Number(payload)
let count = 0
for (let i = 2; i <= n; i++) {
let prime = true
for (let j = 2; j * j <= i; j++) if (i % j === 0) { prime = false; break }
if (prime) count++
}
return count
}"""
)
suspend fun primesBelow(n: Int): Int = primeWorker.call(n.toString()).toInt()
fun shutdown() = primeWorker.close()
Calls on one instance are serialized, one request/reply in flight per worker, so call is safe to invoke from concurrent coroutines; they queue. close() terminates the underlying Worker and is idempotent.
Pass structured data¶
The contract is string in, string out. For anything structured, JSON-encode both directions:
val resizeWorker = WebWorker.of(
"""(payload) => {
const opts = JSON.parse(payload)
const area = opts.width * opts.height
return JSON.stringify({ area: area, budget: Math.round(area * opts.quality) })
}"""
)
val reply = resizeWorker.call("""{"width":1920,"height":1080,"quality":0.8}""")
Decode the returned string with your JSON library of choice. Keep payloads modest; each one is copied into the worker.
Cancel and time out¶
Every request carries a correlation id, and a reply to a cancelled call is dropped instead of being delivered to the next caller. Cancelling the coroutine that awaits call is therefore safe, and withTimeout composes directly:
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.withTimeout
val reply = withTimeout(5.seconds) { worker.call(payload) }
Cancellation abandons the reply; it does not interrupt the JavaScript, so the worker finishes its current job in the background. close() is the way to stop a runaway worker.
Handle failures¶
Failures surface from call as WebWorkerException, with one exception for use-after-close:
| Failure | What call throws |
|---|---|
| The job throws | WebWorkerException carrying the error, with stack when available |
The worker script fails to load, e.g. a syntax error in jobJs |
WebWorkerException, for the in-flight call and every later one |
close() while a call is in flight |
WebWorkerException on that call |
call() on a closed worker |
IllegalStateException |
import io.github.yuroyami.kitecore.WebWorkerException
val outcome = try {
worker.call(payload)
} catch (e: WebWorkerException) {
computeOnMainThread(payload) // fallback
}
A load error is sticky: once the script has failed, the worker cannot recover. close() it and build a new one with WebWorker.of.