-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
[STAGE-2] incomplete implementationRemove this label when implementation is completeRemove this label when implementation is complete[STAGE-2] not fully covered by tests yetRemove this label when tests are verified to cover the implementationRemove this label when tests are verified to cover the implementation[STAGE-2] unresolved discussions leftRemove this label when all critical discussions are resolved on the issueRemove this label when all critical discussions are resolved on the issue[STAGE-3] docs changes not added yetRemove this label when the necessary documentation for the feature / change is addedRemove this label when the necessary documentation for the feature / change is added[STAGE-3] missing 2 reviews for RFC PRsRemove this label when at least 2 core team members reviewed and approved the RFC implementationRemove this label when at least 2 core team members reviewed and approved the RFC implementation
Description
Champion
What's the motivation for this proposal?
Problems you are trying to solve:
- Improve devX & capability to interact with workers
worker$
cannot be terminated- Cannot interact with long lasting web worker through postMessage
worker$
doesn't support streaming
Goals you are trying to achieve:
- Create a low level API to interact with workers to create, run, close, terminate, postMessage and
- Build utils on top of this low level API
- Support streaming output
Any other context or information you want to share:
Proposed Solution / Feature
What do you propose?
A createWorker$(qrl)
that can run the qrl inside a web worker and expose a low level API to interact with it :
const worker = createWorker$(function(params) => {
this.onmessage(() => {
// Do something in the worker when you receive a message
});
this.cleanup(() => {
// Do something when worker is closed
});
// Post a message to the main thread
this.postMessage();
// If something is a function, it'll be used as cleanup, else it'll be send to the main thread
return something;
});
// Open a webworker, run the QRL instead and return a ReadableStream with the value returned by the QRL
const result = await worker.create();
// Start listening on message from the worker
worker.onMessage$(() => );
// Post a message to the worker
await worker.postMessage('With love from the main thread');
// Stop running the current QRL, and trigger all cleanup. But keep the worker alive
await worker.close();
// Terminate the web worker
await worker.terminate();
Code examples
Add support for AbortController to existing worker$
const workerQrl = async (qrl) => {
const worker = createWorkerQrl(qrl);
return (params, { signal }) => new Promise(async (res, rej) => {
const abort = () => {
worker.terminate();
rej(signal.reason);
}
signal.addEventListener('abort', abort, { once: true })
const stream = await worker.create(params);
const { value } = await stream.getReader().read();
res(value);
await worker.terminate();
})
}
Stream data from a worker:
const worker = createWorker$(async function*() {
yield 1;
await new Promise((res) => setTimeout(res, 1000));
yield 2;
});
export default component$(() => {
const counter = useSignal();
useVisibleTask$(async () => {
const stream = await worker.create();
for await (const value of stream) counter.value = value;
})
})
Compute inside a worker :
export default component$(() => {
const counter = useSignal(0);
const result = useSignal<number>();
const worker = createWorker$(() => {
return expensiveCalculation(counter.value);
});
// terminate worker when unmounted
useVisbleTask$(() => worker.terminate);
useVisibleTask$(async ({ track, cleanup }) => {
track(counter);
cleanup(worker.close);
for await (const value of stream) result.value = value;
})
})
Questions :
- Stream
With this implementation we always return aReadableStream
from thecreate()
method to have a native support of AsyncGenerators.
This makes this low level API harder to work with, but since it's low level I thought it's ok.
A better solution might be a mix of Typescript & runtime code to know if the is an AsyncGenerator or not :
// createWorker$ knows the QRL returns an AsyncGenerator so create returns a Promise<ReadableStream>
const stream = await createWorker$(function*() {}).create();
// createWorker$ knows the QRL returns a value so `create` returns a Promise<T>
const result = await createWorker$(() => {}).create();
// createWorker$ knows the QRL returns a cleanup function so `create` returns a Promise<void>
await createWorker$(() => () => /* cleanup */).create();
Do you think we should always return Promise, or it should depends on the QRL output ?
- create
Currentlycreate
open the webworker, run the qrl inside and returns the value if any.
Do you think we should split thecreate
that would open the webworker andapply
that would run the QRL ?
PRs/ Links / References
No response
Metadata
Metadata
Assignees
Labels
[STAGE-2] incomplete implementationRemove this label when implementation is completeRemove this label when implementation is complete[STAGE-2] not fully covered by tests yetRemove this label when tests are verified to cover the implementationRemove this label when tests are verified to cover the implementation[STAGE-2] unresolved discussions leftRemove this label when all critical discussions are resolved on the issueRemove this label when all critical discussions are resolved on the issue[STAGE-3] docs changes not added yetRemove this label when the necessary documentation for the feature / change is addedRemove this label when the necessary documentation for the feature / change is added[STAGE-3] missing 2 reviews for RFC PRsRemove this label when at least 2 core team members reviewed and approved the RFC implementationRemove this label when at least 2 core team members reviewed and approved the RFC implementation
Type
Projects
Status
In Progress (STAGE 2)