Last active
October 16, 2024 13:01
-
-
Save yazaldefilimone/cb052b2cced5017283480880a79bff76 to your computer and use it in GitHub Desktop.
useWorker.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function executeWorker(): Promise<string> { | |
const fakePromise = new Promise<string>((resolve) => { | |
setTimeout(() => { | |
resolve('Hi, its me!'); | |
}, 8000); | |
}); | |
return await fakePromise; | |
} | |
export function Comp(props: Props): JSX.Element { | |
const { data, error, setWorkerFn } = useWorker(executeWorker, { | |
immediate: true, | |
}); | |
// ... | |
setWorkerFn(executeWorker); | |
// ... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useRef, useState, useEffect, useCallback } from 'react'; | |
type workerFnType<T> = () => Promise<T>; | |
interface workerResultType<R> { | |
execute: () => void; | |
setWorkerFn: (fn: workerFnType<R>) => void; | |
data: R | null; | |
error: Error | null; | |
} | |
type WorkerOptions = { | |
immediate?: boolean; | |
}; | |
export function useWorker<R>( | |
workerFn?: workerFnType<R>, | |
options?: WorkerOptions, | |
): workerResultType<R> { | |
const workerRef = useRef<Worker | null>(null); | |
const [data, setData] = useState<R | null>(null); | |
const [error, setError] = useState<Error | null>(null); | |
const createWorker = useCallback( | |
(paramWorkerFn?: workerFnType<R>) => { | |
if (!workerFn && !paramWorkerFn) return {}; | |
const workerCode = ` | |
self.onmessage = async function(e) { | |
const { id } = e.data; | |
try { | |
const func = ${paramWorkerFn?.toString() || workerFn?.toString()}; | |
const data = await func(); | |
self.postMessage({ id, data }); | |
} catch (err) { | |
self.postMessage({ id, error: err.message }); | |
} | |
}; | |
`; | |
const blob = new Blob([workerCode], { type: 'application/javascript' }); | |
const workerUrl = URL.createObjectURL(blob); | |
const worker = new Worker(workerUrl); | |
workerRef.current = worker; | |
return { | |
worker, | |
workerUrl, | |
}; | |
}, | |
[workerFn, options?.immediate], | |
); | |
const execute = useCallback(() => { | |
if (!workerRef.current) return; | |
const { worker, workerUrl } = createWorker(); | |
if (!worker || !workerUrl) return; | |
workerRef.current = worker; | |
return () => { | |
worker.terminate(); | |
URL.revokeObjectURL(workerUrl); | |
}; | |
}, [createWorker]); | |
useEffect(() => { | |
if (!options?.immediate) return; | |
const { worker, workerUrl } = createWorker(); | |
if (!worker || !workerUrl) return; | |
workerRef.current = worker; | |
return () => { | |
worker.terminate(); | |
URL.revokeObjectURL(workerUrl); | |
}; | |
}, [createWorker, options?.immediate]); | |
// handle and update state | |
useEffect(() => { | |
if (!workerRef.current) return; | |
setData(null); | |
setError(null); | |
const messageId = Math.random().toString(36).substring(2, 15); | |
const handleMessage = (e: MessageEvent): void => { | |
const { id, data, error } = e.data; | |
if (id !== messageId) return; | |
if (error) { | |
setError(new Error(error)); | |
return; | |
} | |
setData(data); | |
workerRef.current?.removeEventListener('message', handleMessage); | |
workerRef.current?.removeEventListener('error', handleError); | |
}; | |
const handleError = (e: ErrorEvent): void => { | |
setError(new Error(e.message)); | |
workerRef.current?.removeEventListener('message', handleMessage); | |
workerRef.current?.removeEventListener('error', handleError); | |
}; | |
workerRef.current?.addEventListener('message', handleMessage); | |
workerRef.current?.addEventListener('error', handleError); | |
workerRef.current?.postMessage({ id: messageId }); | |
}, []); | |
const setWorkerFn = useCallback( | |
(fn: workerFnType<R>) => { | |
const { worker, workerUrl } = createWorker(fn); | |
if (!worker || !workerUrl) return; | |
workerRef.current = worker; | |
}, | |
[createWorker, workerRef], | |
); | |
return { execute, data, error, setWorkerFn }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment