Skip to content

Instantly share code, notes, and snippets.

@yazaldefilimone
Last active October 16, 2024 13:01
Show Gist options
  • Save yazaldefilimone/cb052b2cced5017283480880a79bff76 to your computer and use it in GitHub Desktop.
Save yazaldefilimone/cb052b2cced5017283480880a79bff76 to your computer and use it in GitHub Desktop.
useWorker.tsx
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);
// ...
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