Skip to content

Commit

Permalink
feat(troika-worker-utils): add main thread fallback when web workers …
Browse files Browse the repository at this point in the history
…are not allowed

When web workers are not allowed, due to either total lack of support
or local restrictions e.g. CSP, this adds a fallback that runs in the
main thread. Behavior should be exactly the same other than logging a
warning.
  • Loading branch information
lojjic committed May 20, 2020
1 parent a2a487e commit c754d0b
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 3 deletions.
30 changes: 27 additions & 3 deletions packages/troika-worker-utils/src/Thenable.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,35 @@ export function NativePromiseThenable() {
}
}

/**
* Promise.all() impl:
*/
BespokeThenable.all = NativePromiseThenable.all = function(items) {
let resultCount = 0
let results = []
let out = DefaultThenable()
if (items.length === 0) {
out.resolve([])
} else {
items.forEach((item, i) => {
let itemThenable = DefaultThenable()
itemThenable.resolve(item)
itemThenable.then(res => {
resultCount++
results[i] = res
if (resultCount === items.length) {
out.resolve(results)
}
}, out.reject)
})
}
return out
}


/**
* Choose the best Thenable implementation and export it as the default.
*/
export default (
typeof Promise === 'function' ? NativePromiseThenable : BespokeThenable
)
const DefaultThenable = typeof Promise === 'function' ? NativePromiseThenable : BespokeThenable
export default DefaultThenable

24 changes: 24 additions & 0 deletions packages/troika-worker-utils/src/WorkerModules.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Thenable from './Thenable.js'
import { workerBootstrap } from './workerBootstrap.js'
import { defineMainThreadModule } from './mainThreadFallback.js'

let _workerModuleId = 0
let _messageId = 0
Expand All @@ -8,6 +9,24 @@ const workers = Object.create(null)
const openRequests = Object.create(null)
openRequests._count = 0

let supportsWorkers = () => {
let supported = false
try {
// TODO additional checks for things like importScripts within the worker?
// Would need to be an async check.
let worker = new Worker(
URL.createObjectURL(
new Blob([''], {type: 'application/javascript'})
)
)
worker.terminate()
supported = true
} catch(err) {
console.warn(`Troika createWorkerModule: web workers not allowed in current environment; falling back to main thread execution.`, err)
}
supportsWorkers = () => supported
return supported
}

/**
* Define a module of code that will be executed with a web worker. This provides a simple
Expand Down Expand Up @@ -46,6 +65,11 @@ export function defineWorkerModule(options) {
throw new Error('requires `options.init` function')
}
let {dependencies, init, getTransferables, workerId} = options

if (!supportsWorkers()) {
return defineMainThreadModule(options)
}

if (workerId == null) {
workerId = '#default'
}
Expand Down
38 changes: 38 additions & 0 deletions packages/troika-worker-utils/src/mainThreadFallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Thenable from './Thenable.js'

/**
* Fallback for `defineWorkerModule` that behaves identically but runs in the main
* thread, for when the execution environment doesn't support web workers or they
* are disallowed due to e.g. CSP security restrictions.
*/
export function defineMainThreadModule(options) {
let moduleFunc = function(...args) {
return moduleFunc._getInitResult().then(initResult => {
if (typeof initResult === 'function') {
return initResult(...args)
} else {
throw new Error('Worker module function was called but `init` did not return a callable function')
}
})
}
moduleFunc._getInitResult = function() {
// We can ignore getTransferables in main thread. TODO workerId?
let {dependencies, init} = options

// Resolve dependencies
dependencies = Array.isArray(dependencies) ? dependencies.map(dep =>
dep && dep._getInitResult ? dep._getInitResult() : dep
) : []

// Invoke init with the resolved dependencies
let initThenable = Thenable.all(dependencies).then(deps => {
return init.apply(null, deps)
})

// Cache the resolved promise for subsequent calls
moduleFunc._getInitResult = () => initThenable

return initThenable
}
return moduleFunc
}

0 comments on commit c754d0b

Please sign in to comment.