@pricingmonkey/tangi
v4.0.1
Published
Lightweight actor library for Web Workers inspired by Akka
Readme
👧 tangi
ತಂಗಿ [tangi] Kan. younger sister
ಅಕ್ಕ [akka] Kan. older sister
Lightweight actor library for Web Workers inspired by Akka.
What is this?
Type-safe, production-ready and lightweight messaging layer for Web Workers.
Why?
For people to scale Web Workers beyond the simple patterns of communication.
Basic usage
Run npx http-server . and open index.html:
index.html
<script type="module">
import { makeActorContext } from "https://esm.sh/@pricingmonkey/tangi";
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
const workerRemoteContext = makeActorContext(worker);
const response = workerRemoteContext.ask(id => ({ _tag: "PING", id }));
switch (response._tag) {
case "Right": {
console.log(response.right);
break;
}
case "Left": {
console.error(response.left);
break;
}
}
</script>worker.js
import { makeActorContext } from "https://esm.sh/@pricingmonkey/tangi";
const workerLocalContext = makeActorContext(self);
workerLocalContext.receiveMessage(message => {
switch (message._tag) {
case "PING": {
return { _tag: "Right", right: "PONG" };
}
}
});Running demo projects
yarn demo:web-workeryarn demo:shared-worker
Interaction patterns (TypeScript)
Best served with:
- https://github.com/webpack-contrib/worker-loader
Fire and forget
Use workerRemoteContext.tell({ _tag: "FIRE" }). See example below:
messages.ts
type FireMessage = {
_tag: "FIRE";
}main.ts
import { makeActorContext } from "tangi";
import { FireMessage } from "./messages";
const worker = new (require("worker-loader!./worker"))();
const workerRemoteContext = makeActorContext<FireMessage, never>(worker);
workerRemoteContext.tell({ _tag: "FIRE" });worker.ts
import { makeActorContext } from "tangi";
import { FireMessage } from "./messages";
const workerLocalContext = makeActorContext<never, FireMessage>(self as any);
workerLocalContext.receiveMessage(message => {
switch (message._tag) {
case "FIRE": {
// trigger some logic in the WebWorker
}
}
});Request-response
Use workerRemoteContext.ask({ _tag: "PING" }) combined with workerLocalContext.receiveMessage({ _tag: "PING" }). See example below:
messages.ts
type PingMessage = {
_tag: "PING";
}
type PongMessage = {
_tag: "PONG";
}main.ts
import { makeActorContext } from "tangi";
import { PingMessage, PongMessage } from "./messages";
const worker = new (require("worker-loader!./worker"))();
const workerRemoteContext = makeActorContext<PingMessage, never>(worker);
const response = workerRemoteContext.ask<string, PongMessage>(id => ({ _tag: "PING", id }));
console.log(response)worker.ts
import { makeActorContext } from "tangi";
import { PongMessage } from "./messages";
const workerLocalContext = makeActorContext<never, PongMessage>(self as any);
workerLocalContext.receiveMessage(message => {
switch (message._tag) {
case "PING": {
return { _tag: "PONG" };
}
}
});Cancellation (single message)
worker.ts
import { makeActorContext, makeCancellationOperator } from "tangi";
type PingMessage = {
_tag: "PING";
id: string;
}
type CancelMessage = {
_tag: "CANCEL";
id: string;
}
const makeTask = (killSwitch) => {
return async () => {
for (let i = 0; i < 100000; i++) {
await fetch("http://example.org");
if (killSwitch.isCancelled) {
return;
}
}
}
};
const workerLocalContext = makeActorContext<never, PongMessage>(self as any);
const cancellationOperator = makeCancellationOperator();
workerLocalContext.receiveMessage(async message => {
switch (message._tag) {
case "PING": {
const cancellableTask = cancellationOperator.register(message.id, message.id, makeTask);
try {
await task.promise();
} finally {
cancellationOperator.unregister(message.id, message.id);
}
return;
}
case "CANCEL": {
cancellationOperator.cancel(message.id);
return;
}
}
});Cancellation (message groups)
Use it to cancel multiple messages in a given group/context.
Similar to Cancellation (single message) and:
- types become:
type PingMessage = {
_tag: "PING";
groupId: string;
id: string;
}
type CancelMessage = {
_tag: "CANCEL";
groupId: string;
id: string;
}- cancellation operator calls change to:
cancellationOperator.register(message.contextId, message.id, task);
cancellationOperator.unregister(message.contextId, message.id);
cancellationOperator.cancel(message.contextId);