Skip to main content

Web Worker Examples

This section demonstrates how to use @renderdev/threadpool in web browser environments. These examples showcase how to leverage ThreadPool's API for both dedicated and shared Web Workers.

Reference Files

The following code examples will build upon these files.

<!doctype html>
<html>
<head>
<title>Web Workers</title>
<script src="thread-pool.js" type="module" defer></script>
</head>
<body>
<h1>Browser Web Workers Example</h1>
</body>
</html>

Dedicated Web Workers

Dedicated Web Workers are the most common type of web worker, providing an isolated thread for executing JavaScript that's tied to a single context.

Basic Dedicated Workers

import { StatusType, WebFunctionPool, importWebWorker, importTaskWebWorker } from '@renderdev/threadpool/web'
import type * as mathType from './math.ts'

const start = performance.now()

console.log(`Number of Treads: ${navigator.hardwareConcurrency}`)
console.log(`Starting...\n`)

const pool = (new WebFunctionPool())
.allSettled(() => {
const completed = pool.status('completed', StatusType.RAW)

for (const thread of completed) {
console.log(thread.message, thread.status.SUCCESS ? 'Success' : 'Error', thread.meta)
}

console.log('\nDONE!', pool.status('completed', StatusType.COUNT))
console.log('\n\nScript runtime: ' + (Math.round(performance.now() - start) / 1000) + ' sec\n\n')
})

const filename = new URL('./math.js', import.meta.url)
const { add, getState } = await importWebWorker<typeof mathType>(filename)
const { fib, state } = await importTaskWebWorker<typeof mathType>(filename)

let threads = Array.from({ length: 10 })
for (const i in threads) {
pool.addTask(fib(42), +i + 1)
}
pool.addTask(() => getState(), 11)
pool.addTask(() => add(1, 2), 12)
pool.addTask(() => getState(), 13)
pool.addTask(() => add(34, 56), 14)
pool.addTask(() => getState(), 15)
pool.addTask(state(), 16)
tip

This code highlights the difference between importWebWorker and importTaskWebWorker. They do the same thing, except importTaskWebWorker will "promisify" the callback. This allows us to directly pass the response as a task for a WebFunctionPool. In other words, its syntactic sugar to make the code easier to write and read when dealing with tasks.

Type checking will still work in both instances.

Persistent Dedicated Workers

You can create multiple persistent dedicated workers and use them in parallel:

import { StatusType, WebFunctionPool, importPersistentWebWorker } from '@renderdev/threadpool/web'
import type * as mathType from './math.ts'

const start = performance.now()

console.log(`Number of Treads: ${navigator.hardwareConcurrency}`)
console.log(`Starting...\n`)

const filename = new URL('./math.js', import.meta.url)

const mathB = await importPersistentWebWorker<typeof mathType>(filename)

const pool = new WebFunctionPool()
pool.allSettled(() => {
const completed = pool.status('completed', StatusType.RAW)
for (const thread of completed) {
console.log(thread.message, thread.status.SUCCESS ? 'Success' : 'Error', thread.meta)
if (thread.meta === 'B2') {
mathB.terminate()
}
}

console.log('\nDONE!', pool.status('completed', StatusType.COUNT))
console.log('\n\nScript runtime: ' + (Math.round(performance.now() - start) / 1000) + ' sec\n\n')
})

pool.addTask(async () => {
const math = await importPersistentWebWorker<typeof mathType>(filename)
console.log('A: getState(): ', await math.state())
console.log('A: add(1, 2): ', await math.add(1, 2))
console.log('A: getState(1, 2): ', await math.state())
console.log('A: add(3, 4): ', await math.add(3, 4))
console.log('A: getState(3, 4): ', await math.state())
console.log('A: fib(): ', await math.fib(42))
math.terminate()
return 'Done A!'
}, 'A')

pool.addTask(async () => {
console.log('B: getState(): ', await mathB.state())
console.log('B: add(5, 6): ', await mathB.add(5, 6))
console.log('B: getState(5, 6): ', await mathB.state())
console.log('B: add(7, 8): ', await mathB.add(7, 8))
console.log('B: getState(7, 8): ', await mathB.state())
console.log('B: fib(): ', await mathB.fib(41))
return 'Done B!'
}, 'B')

pool.addTask(async () => {
const math = await importPersistentWebWorker<typeof mathType>(filename)
console.log('C: getState(): ', await math.state())
console.log('C: add(9, 10): ', await math.add(9, 10))
console.log('C: getState(9, 10): ', await math.state())
console.log('C: add(11, 12): ', await math.add(11, 12))
console.log('C: getState(11, 12): ', await math.state())
console.log('C: fib(): ', await math.fib(40))
math.terminate()
return 'Done C!'
}, 'C')

setTimeout(() => {
pool.addTask(async () => {
console.log('B2: getState(): ', await mathB.state())
console.log('B2: add(5, 6): ', await mathB.add(5, 6))
console.log('B2: getState(5, 6): ', await mathB.state())
console.log('B2: add(7, 8): ', await mathB.add(7, 8))
console.log('B2: getState(7, 8): ', await mathB.state())
console.log('B2: fib(): ', await mathB.fib(41))
return 'Done B2!'
}, 'B2')
}, 2000)

Using Shared Web Workers

Shared Web Workers can be accessed from multiple browser contexts like windows, tabs, or iframes, allowing for communication between different parts of your application.

Basic Shared Workers Example

import { StatusType, WebFunctionPool, importWebWorker, importTaskWebWorker } from '@renderdev/threadpool/web'
import type * as mathType from './math.ts'

const start = performance.now()

console.log(`Number of Treads: ${navigator.hardwareConcurrency}`)
console.log(`Starting...\n`)

const filename = new URL('./math.js', import.meta.url)
const WorkerOptions = { type: 'module', credentials: 'same-origin' }

const pool = new WebFunctionPool()
pool.allSettled(() => {
const completed = pool.status('completed', StatusType.RAW)

for (const thread of completed) {
console.log(thread.message, thread.status.SUCCESS ? 'Success' : 'Error', thread.meta)
}

console.log('\nDONE!', pool.status('completed', StatusType.COUNT))
console.log('\n\nScript runtime: ' + (Math.round(performance.now() - start) / 1000) + ' sec\n\n')
})

const { add, getState } = await importWebWorker<typeof mathType>(filename)
const { fib, state } = await importTaskWebWorker<typeof mathType>(filename)

let threads = Array.from({ length: 10 })
for (const i in threads) {
pool.addTask(fib(42), +i + 1)
}
pool.addTask(() => getState(), 11)
pool.addTask(() => add(1, 2), 12)
pool.addTask(() => getState(), 13)
pool.addTask(() => add(34, 56), 14)
pool.addTask(() => getState(), 15)
pool.addTask(state(), 16)
tip

This code highlights the difference between importWebWorker and importTaskWebWorker. They do the same thing, except importTaskWebWorker will "promisify" the callback. This allows us to directly pass the response as a task for a WebFunctionPool. In other words, its syntactic sugar to make the code easier to write and read when dealing with tasks.

Type checking will still work in both instances.

warning

Shared Web Workers are not supported in all browsers. Make sure to check compatibility or provide a fallback to dedicated workers.

Persistent Shared Web Workers

For scenarios where you want to maintain shared state across multiple browser contexts:

import { StatusType, WebFunctionPool, importPersistentWebWorker } from '@renderdev/threadpool/web'
import type * as mathType from './math.ts'

const start = performance.now()

console.log(`Number of Treads: ${navigator.hardwareConcurrency}`)
console.log(`Starting...\n`)

const filename = new URL('./math.js', import.meta.url)
const WorkerOptions = { type: 'module', credentials: 'same-origin' }

const mathB = await importPersistentWebWorker<typeof mathType>(filename, WorkerOptions, { WorkerType: SharedWorker })

const pool = new WebFunctionPool()
pool.allSettled(() => {
const completed = pool.status('completed', StatusType.RAW)
for (const thread of completed) {
console.log(thread.message, thread.status.SUCCESS ? 'Success' : 'Error', thread.meta)
if (thread.meta === 'B2') {
mathB.terminate()
}
}

console.log('\nDONE!', pool.status('completed', StatusType.COUNT))
console.log('\n\nScript runtime: ' + (Math.round(performance.now() - start) / 1000) + ' sec\n\n')
})

pool.addTask(async () => {
const math = await importPersistentWebWorker<typeof mathType>(filename, WorkerOptions, { WorkerType: SharedWorker })
console.log('A: getState(): ', await math.state())
console.log('A: add(1, 2): ', await math.add(1, 2))
console.log('A: getState(1, 2): ', await math.state())
console.log('A: add(3, 4): ', await math.add(3, 4))
console.log('A: getState(3, 4): ', await math.state())
console.log('A: fib(): ', await math.fib(42))
math.terminate()
return 'Done A!'
}, 'A')

pool.addTask(async () => {
console.log('B: getState(): ', await mathB.state())
console.log('B: add(5, 6): ', await mathB.add(5, 6))
console.log('B: getState(5, 6): ', await mathB.state())
console.log('B: add(7, 8): ', await mathB.add(7, 8))
console.log('B: getState(7, 8): ', await mathB.state())
console.log('B: fib(): ', await mathB.fib(41))
return 'Done B!'
}, 'B')

pool.addTask(async () => {
const math = await importPersistentWebWorker<typeof mathType>(filename, WorkerOptions, { WorkerType: SharedWorker })
console.log('C: getState(): ', await math.state())
console.log('C: add(9, 10): ', await math.add(9, 10))
console.log('C: getState(9, 10): ', await math.state())
console.log('C: add(11, 12): ', await math.add(11, 12))
console.log('C: getState(11, 12): ', await math.state())
console.log('C: fib(): ', await math.fib(40))
math.terminate()
return 'Done C!'
}, 'C')

setTimeout(() => {
pool.addTask(async () => {
console.log('B2: getState(): ', await mathB.state())
console.log('B2: add(5, 6): ', await mathB.add(5, 6))
console.log('B2: getState(5, 6): ', await mathB.state())
console.log('B2: add(7, 8): ', await mathB.add(7, 8))
console.log('B2: getState(7, 8): ', await mathB.state())
console.log('B2: fib(): ', await mathB.fib(41))
return 'Done B2!'
}, 'B2')
}, 4000)
info

With Shared Web Workers, the state is shared across all connections. When one task updates the state, all other connections will see the updated value.