Build concurrent programs with Arc’s actor model, powered by the BEAM VM
Arc brings Erlang’s legendary actor model to JavaScript. Actors are lightweight processes that communicate through message passing, enabling you to write highly concurrent, fault-tolerant programs.
The key insight: count is private. Only the counter process can modify it. Other processes must send messages to interact with it.
Run the counter:
arc examples/counter_actor.js
Output:
counter: incremented by 10 -> now 10counter: incremented by 5 -> now 15counter: decremented by 3 -> now 12counter: incremented by 100 -> now 112Main got counter value: 112counter: stopping with final value 112Counter stopped with: 112
Pass messages through a ring of processes. From examples/ring.js:
examples/ring.js (simplified)
var N = 100; // number of processes in the ringvar M = 10; // number of laps around the ring// Build a ring of N processesvar first = Arc.self();var prev = first;var i = N;while (i > 0) { var next = prev; prev = Arc.spawn(() => { while (true) { var msg = Arc.receive(); if (msg.type === 'stop') { return; } Arc.send(next, { type: 'token', lap: msg.lap, hops: msg.hops + 1 }); } }); i = i - 1;}// Send the token into the ringArc.send(prev, { type: 'token', lap: 0, hops: 0 });
This creates 100 processes and passes a message around the ring 10 times — a classic Erlang benchmark demonstrating lightweight process creation.
Processes run truly concurrently on the BEAM scheduler. Here’s examples/concurrent.js:
examples/concurrent.js
function run(name, delay) { Arc.log(Arc.self(), 'is starting'); let i = 1; while (true) { Arc.log(`${name} tick`, i); i = i + 1; Arc.sleep(delay); }}Arc.spawn(() => run('[Process A]', 50));Arc.spawn(() => run(' [Process B]', 200));Arc.log(' [Main] sleeping... watch A and B interleave!');Arc.sleep(800);Arc.log(' [Main] Done!');
Run it:
arc examples/concurrent.js
You’ll see the BEAM interleaving Process A and Process B in real-time — each running on its own scheduler, with different sleep intervals.
Every spawned process gets its own copy of the JavaScript VM state. Changes in one process don’t affect others. From examples/mutability.js:
examples/mutability.js
let i = 0;const main = Arc.self();Arc.spawn(() => { i = 10; Arc.log(Arc.self(), 'I set `i` to', i); Arc.send(main); // sends undefined});Arc.log(main, 'but I see that `i` is', i);
Output:
Pid<0.124.0> I set `i` to 10Pid<0.123.0> but I see that `i` is 0
Each process has its own heap. Spawning copies the VM state, so mutating variables in a child process doesn’t affect the parent.
This is different from JavaScript threads or workers — Arc actors are true processes with isolated memory, not shared-memory threads.
Supervision trees and fault tolerance features are planned for future Arc releases. For now, actors don’t automatically restart on crash.
In Erlang/OTP, actors are organized into supervision trees where supervisors automatically restart crashed workers. Arc will support this pattern in the future, bringing Erlang’s “let it crash” philosophy to JavaScript.