-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
index.ts
134 lines (114 loc) · 3.49 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* this implementation is a vue port of https://github.com/alewin/useWorker by Alessio Koci */
import { ref } from 'vue-demi'
import { tryOnScopeDispose } from '@vueuse/shared'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
import createWorkerBlobUrl from './lib/createWorkerBlobUrl'
export type WebWorkerStatus =
| 'PENDING'
| 'SUCCESS'
| 'RUNNING'
| 'ERROR'
| 'TIMEOUT_EXPIRED'
export interface UseWebWorkerOptions extends ConfigurableWindow {
/**
* Number of milliseconds before killing the worker
*
* @default undefined
*/
timeout?: number
/**
* An array that contains the external dependencies needed to run the worker
*/
dependencies?: string[]
}
/**
* Run expensive function without blocking the UI, using a simple syntax that makes use of Promise.
*
* @see https://vueuse.org/useWebWorkerFn
* @param fn
* @param options
*/
export const useWebWorkerFn = <T extends (...fnArgs: any[]) => any>(
fn: T,
options: UseWebWorkerOptions = {},
) => {
const {
dependencies = [],
timeout,
window = defaultWindow,
} = options
const worker = ref<(Worker & { _url?: string }) | undefined>()
const workerStatus = ref<WebWorkerStatus>('PENDING')
const promise = ref<({ reject?: (result: ReturnType<T> | ErrorEvent) => void;resolve?: (result: ReturnType<T>) => void })>({})
const timeoutId = ref<number>()
const workerTerminate = (status: WebWorkerStatus = 'PENDING') => {
if (worker.value && worker.value._url && window) {
worker.value.terminate()
URL.revokeObjectURL(worker.value._url)
promise.value = {}
worker.value = undefined
window.clearTimeout(timeoutId.value)
workerStatus.value = status
}
}
workerTerminate()
tryOnScopeDispose(workerTerminate)
const generateWorker = () => {
const blobUrl = createWorkerBlobUrl(fn, dependencies)
const newWorker: Worker & { _url?: string } = new Worker(blobUrl)
newWorker._url = blobUrl
newWorker.onmessage = (e: MessageEvent) => {
const { resolve = () => {}, reject = () => {} } = promise.value
const [status, result] = e.data as [WebWorkerStatus, ReturnType<T>]
switch (status) {
case 'SUCCESS':
resolve(result)
workerTerminate(status)
break
default:
reject(result)
workerTerminate('ERROR')
break
}
}
newWorker.onerror = (e: ErrorEvent) => {
const { reject = () => {} } = promise.value
reject(e)
workerTerminate('ERROR')
}
if (timeout) {
timeoutId.value = setTimeout(
() => workerTerminate('TIMEOUT_EXPIRED'),
timeout,
) as any
}
return newWorker
}
const callWorker = (...fnArgs: Parameters<T>) =>
new Promise<ReturnType<T>>((resolve, reject) => {
promise.value = {
resolve,
reject,
}
worker.value && worker.value.postMessage([[...fnArgs]])
workerStatus.value = 'RUNNING'
})
const workerFn = (...fnArgs: Parameters<T>) => {
if (workerStatus.value === 'RUNNING') {
console.error(
'[useWebWorkerFn] You can only run one instance of the worker at a time.',
)
/* eslint-disable-next-line prefer-promise-reject-errors */
return Promise.reject()
}
worker.value = generateWorker()
return callWorker(...fnArgs)
}
return {
workerFn,
workerStatus,
workerTerminate,
}
}
export type UseWebWorkerFnReturn = ReturnType<typeof useWebWorkerFn>