Er.js: Erlang-in-JavaScript

Erlang-style concurrency with JavaScript 1.7

Download Er.js | See it in action


About

Er.js piggybacks on Neil Mix’s Thread.js which fakes threading in JavaScript 1.7 using coroutines and nested generator continuations. The goal is to replicate Erlang’s concurrent lockless process model and message-passing APIs in JavaScript.

Running Concurrently

Running a JavaScript function in the background is easy with Er.js:

Er.spawn(myBackgroundFunction);

Er.spawn starts a new Er.js process running myBackgroundFunction, and returns its process id.

Because processes are really coroutines, you have to call yield before any function which might block. Calling yield will create a continuation trampoline that can be rerun by Er.js when it’s time for the process to continue executing. For example:

function myBackgroundFunction() {
   // Wait for 4 seconds
   yield Er.sleep(4000);
   // Do other things…
}

Sending Messages

In Erlang and Er.js, each process has a built-in message queue that other processes use to send it messages. Posting to the message queue never blocks the caller, and the destination process for the message can read them off the queue whenever it wants. Messages are just regular associative arrays, similar to hashtables, which are easy to create in JavaScript. For example:

Er.send(myPid, { Hello: new Date(), From: Er.pid() });

Here, myPid is assumed to be the process id from some former call to Er.spawn. This call sends a message with the keys “Hello” and “From”. Hello is a Date object with the current date, and From is the process id of the current process, which can always be fetched with Er.pid(). Passing the current pid means the myPid process can send us messages in return, since we’ve told it who we are.

Receiving & Pattern Matching

When myBackgroundFunction wants to read off its message queue, it calls the Er.receive function, telling it the kind of message it’s interested in, and a function to call when such a message is received. Interest in a message is expressed using a message pattern which, just like the messages themselves, is a simple hash table.

yield Er.receive({ Hello: Date, From: _ },     // pattern
                 function(msg) {               // handler
                    log(”Hello=” + msg.Hello);
                    log(”From=” + msg.From);
                 });

This matches any message in the current process’s queue which has a Hello key with a Date object as the value, and with a From key with any value. Explicit value matching for e.g. number and string literals and DOM elements is also possible. The “_” for the From key means that any value is accepted. There are a few other matching rules as well that make this a very powerful but simple message dispatching mechanism.

If a message in the process queue matches a pattern passed to Er.receive, it is passed to the handler function found in the argument list following the pattern. The handler can look up the key values it needs in order to act on the message. It can also send messages to other processes, spawn new processes, receive queued messages or perform other work.

Because the Er.receive call doesn’t finish until a message matching one of patterns is received and handled, we put a yield in front of the call to avoid blocking other processes.

Linked Processes

When a process finishes or exits, it automatically sends a message to any processes which link to it. Linking is done by passing a pid to Er.link. The sent message is of the form:

{ Signal: Er.Exit, From: exiting_pid, Reason: reason }

The Reason value comes either from the exiting process calling Er.exit(reason), or just throw'ing the reason as an exception. If the linking process does not handle this message, it will exit itself, sending exit messages to its own linked processes. This allows for simple process chaining and failure handling.

Message Multicast

Er.js processes can also register to receive messages sent to a given name, using Er.register(name). Registered names can be passed as the first argument to Er.send. Multiple processes can register for the same name, and they will all receive a message sent to that name, allowing for simple multi-casting.

Concurrent AJAX with Er.Ajax

XmlHttpRequest, AJAX and JSON integrate nicely with the process and message-passing model, allowing processes to avoid asynchronous JavaScript and callbacks.

Instead, using message-passing and concurrency, Er.js makes network access transparent, without blocking other processes or interactivity:

var txt = yield Er.Ajax.get("http://beatniksf.com/erjs/index.html");
alert("Fetched Content: " + txt);

Under the covers, this is accomplished using Er.Ajax.start, which handles asynchronous XmlHttpRequest internals and uses Er.send to tell the current process about download progress and completion.

Er.Ajax.get and others (post, json, etc) are implemented by yielding execution until the final message from Er.Ajax.start is received. If the message indicates success, the response text is returned to the caller, otherwise the failure message is thrown:

function myGet(url) {
   Er.Ajax.start(url);
   yield Er.receive({ Url: url, Success: true, Text: String, _:_ },
                    function(msg) { return msg.Text; },
                    { Url: url, Success: false, Text: String, _:_ },
                    function(msg) { throw msg; });
}

Er.DOM Event Handling

As with remote resources, Er.js can concurrently listen for DOM Events, and once received insert them into the listening process's message queue. The following demonstrates a simple wrapper, Er.DOM.Receive, which subscribes to a named event on a DOM element and calls Er.receive to await its delivery:

var event = yield Er.DOM.receive(document, "click");

Note: The best approach to handling DOM manipulation and eventing with Er.js is still under investigation. For instance, document above might alternately refer to an an element's id or multiple elements using a CSS selector.

Future Work

Er.js will wrap certain long-running asynchronous JavaScript calls in synchronous yielding wrappers so that processes can avoid convoluted asynchronous code.

JSON-based Remote Procedure Calls

Like Erlang itself, Er.js will enable easy interaction with concurrent processes running on remote nodes. Using JSON and XML, messages can be sent to and received from remote servers using the same API as locally running concurrent processes:
var rpc = Er.spawn("http://www.beatniksoftware.com/echo");
...
Er.send(rpc, { Echo: "This is the echo payload" });
yield Er.receive({ EchoResponse: String },
                 function (msg) {
                    alert("Got Echo response: " + msg.EchoResponse);
                 });

Native Threading

It would also be interesting to explore the use of native threads for running processes, leveraging e.g. Google Gears' WorkerPools if available on the client.


Er.js is from Alex Graveley (www).