Skip to content

Lightweight actor library for Web Workers inspired by Akka

License

Notifications You must be signed in to change notification settings

pricingmonkey/tangi

Repository files navigation

👧 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-worker
  • yarn demo:shared-worker

Interaction patterns (TypeScript)

Best served with:

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);

License

Blue Oak Model License

About

Lightweight actor library for Web Workers inspired by Akka

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •