Port Agent
A RPC-like facility for making inter-thread function calls.
Features
- Port Agent will marshall the return value or
Error
back to the caller. - Registered functions (i.e.,
Agent.register
) are persistent and can be deregistered using the Agent.deregister
method. - Late binding registrants will be called with previously awaited invocations.
Table of Contents
- API
- Usage
- Examples
API
The Agent
Class
port_agent.Agent(port)
- port
<threads.MessagePort>
or <threads.Worker>
The message port.
agent.call<T>(name, ...args)
agent.register(name, fn)
agent.deregister(name)
Usage
You can create a new Agent
by passing a parentPort
or a Worker
instance to the Agent
constructor:
const worker = new Worker(fileURLToPath(import.meta.url));
const agent = new Agent(worker);
You can register a function in the main thread or in a Worker thread using the Agent.register
method:
agent.register('hello_world', (value: string): string => `Hello ${value} world!`);
You can call a function in the main thread or in a Worker thread using the Agent.call
method:
const greeting = await agent.call<string>('hello_world', 'happy');
Examples
An Example
In this example you will:
- Instantiate a Worker thread.
- Instantiate an Agent in the Main thread.
- Use the Agent to call the
hello_world
function.
- At this point the
hello_world
function has not yet been registered in the Worker thread.
- Wait for the Worker to come online.
- Instantiate an Agent in the Worker thread.
- Use the Agent to register the
hello_world
function in the Worker. - Use the Agent to register the
a_reasonable_assertion
function in the Worker. - Use the Agent to call the function registered as
hello_world
. - Await (3) and log the return value.
- Await (8) and log the return value.
- Use the Agent to call the function registered as
a_reasonable_assertion
. - Await (11) and catch the Error and log the stack trace in the Main thread.
- The Error was marshalled from the Error produced by the reasonable assertion that was made in the
nowThrowAnError
function in the Worker thread.
- Terminate the Worker asynchronously.
- Await abends.
- The Worker exited; hence, log the exit code.
- If an unhandled exception had occured in the Worker it would have been handled accordingly.
Please see the comments in the code that specify each of the steps above.
./tests/test/index.ts
import { Worker, isMainThread, parentPort } from 'node:worker_threads';
import { fileURLToPath } from 'node:url';
import { strict as assert } from 'node:assert';
import { Agent } from 'port_agent';
if (isMainThread) {
void (async () => {
const worker = new Worker(fileURLToPath(import.meta.url));
const agent = new Agent(worker);
worker.on('online', async () => {
try {
const greeting = await agent.call<string>('hello_world', 'again, another');
console.log(greeting);
await agent.call('a_reasonable_assertion', 'To err is Human.');
}
catch (err) {
console.error(`Now, back in the Main Thread, we will handle the`, err);
}
finally {
void worker.terminate();
try {
await agent.call<string>('hello_world', 'no more...');
}
catch (err) {
if (err instanceof Error) {
console.error(err);
}
else if (typeof err == 'number') {
console.log(`Exit code: ${err.toString()}`);
}
}
}
});
const greeting = await agent.call<string>('hello_world', 'another');
console.log(greeting);
})();
} else {
function nowThrowAnError(message: string) {
assert.notEqual(typeof new Object(), typeof null, message);
}
function callAFunction(message: string) {
nowThrowAnError(message);
}
if (parentPort) {
const agent = new Agent(parentPort);
agent.register('hello_world', (value: string): string => `Hello ${value} world!`);
agent.register('a_reasonable_assertion', callAFunction);
}
}
This example should log to the console something that looks similar to this:
Hello another world!
Hello again, another world!
Now, back in the Main Thread, we will handle the AssertionError [ERR_ASSERTION]: To err is Human.
at nowThrowAnError (file:///port_agent/tests/test/dist/index.js:31:16)
at callAFunction (file:///port_agent/tests/test/dist/index.js:34:9)
at Agent.tryPost (/port_agent/dist/index.js:92:33)
at MessagePort.<anonymous> (/port_agent/dist/index.js:62:36)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:762:20)
at exports.emitMessage (node:internal/per_context/messageport:23:28) {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'object',
expected: 'object',
operator: 'notStrictEqual'
}
Exit code: 1
Run the test.
You can run the test using:
npm run test