Worker Communication

The webR worker thread

WebR initialises by launching a version of R built for WebAssembly in a JavaScript web worker. This allows R to perform long running computation without blocking the main browser thread. Without this process, the browser page loading webR would freeze whenever R is performing work.

Communication channels

Communication between the main thread and the webR worker thread is managed by message passing through a communication channel. The possible channel types are:

Name Requirements Limitations
SharedArrayBuffer (Default) Cross-origin Isolation None
ServiceWorker JavaScript service workers A service worker script must be hosted in the same origin as the page loading webR.
PostMessage R code cannot be interrupted. Nested R REPLs, e.g. browser(), do not work.

A communication channel will be automatically selected at startup, defaulting to SharedArrayBuffer if the page is cross-origin isolated. It is also possible to manually select a channel type by setting the WebROptions.channelType configuration option at startup.

Warning

Only one JavaScript service worker can be active at at time for a given web page. As such, if you plan to use your own service worker in the web application making use of webR, then you must either use another communication channel, or integrate the required webR communication message passing into your own service worker.

JavaScript promises

Since messages and data are communicated to and from the worker thread asynchronously, most of the API methods provided by webR return results through JavaScript Promises rather than returning results directly.

In a modern asynchronous JavaScript environment (e.g. within an async function), the result of a promise can easily be obtained by using the await keyword. For example,

await webR.read();
{ type: 'stdout', data: 'R is a collaborative project with many contributors.' }

Alternative use of promises

As an alternative, the then() method can be used with JavaScript promises and callbacks when not in an asynchronous environment to obtain results without requiring the use of async or await,

webR.read().then((message) => {
  console.log(message);
});
{ type: 'stdout', data: 'R is a collaborative project with many contributors.' }

Message passing and requests

Communication between the main thread and the webR worker thread is in the form of messages sent over an established communication channel. A web application making use of webR should send input messages and handle output messages.

The webR main thread maintains both input and an output queues. All messages sent from the main thread to the worker are stored in the input queue until they are consumed by the webR worker thread. If the input queue is empty, the worker worker thread blocks until an input message has been received.

Messages from the worker thread are stored in the output queue on the main thread until they are read and consumed by the application loading and using webR.

Message interface

Communication messages are in the form of a JavaScript object with type Message, which in general have shape,

{ type: string, data: any }

From the main thread, messages can be sent to the webR worker thread using WebR.write().

Messages from the worker thread can be obtained from the output queue using WebR.read() to receive the next message in the queue, or WebR.flush() to receive an array of all queued output messages. These methods both return promises resolving to the output messages.

The WebR.close() method closes down the communication channel. This stops the webR worker thread and should only be used once communication with an R process is no longer required.

Handling messages

A useful pattern for a web application making use of webR is to handle output messages in an asynchronous loop,

async function run() {
  for (;;) {
    const output = await webR.read();
    switch (output.type) {
      case 'stdout':
        console.log(output.data);
        break;
      case 'stderr':
        console.error(output.data);
        break;
      default:
        console.warn(`Unhandled output type: ${output.type}.`);
    }
  }
}

As an alternative to manually managing messages in this way, the Console class is available on the main WebR module which can be used to implement a simple R console using JavaScript callbacks.

Input messages

Standard input stream

Messages of type stdin are sent to the R standard input stream, and a convenience function WebR.writeConsole() is provided to send messages of this type.

Messages of type stdin should provide a line of input in the form of a string as the message data.

{ type: 'stdin', data: string }

Package installation

A request to install a package from the webR binary package repository can be made with messages of type installPackage, providing the name of the requested package to be installed in the form of a string,

{ type: 'installPackage', data: { name: string } }

A convenience function WebR.installPackages() is provided, taking an array of package names to be installed.

Interruption

A long running R computation can be interrupted by sending an interruption request message using the WebR.interrupt() method.

Output messages

Standard output streams

Messages of type stdout or stderr are sent to the output queue for each line of standard stream output given by R. The line of output is given in the data property.

{ type: 'stdout' | 'stderr', data: string }

Prompt

Messages of type prompt are sent to the output queue when R has given a prompt and is waiting for input. The prompt string, e.g. '> ', is given in the data property.

{ type: 'prompt', data: string }

Closed

A message of type closed is issued when the webR worker thread and communication channel have been terminated using WebR.close(). This message indicates that the webR instance should no longer be used and no further messages will be issued. The data property is undefined for this type of message.

{ type: 'closed' }

This type of message can be a useful signal to break out of an otherwise infinite asynchronous loop of reading webR output messages.

Canvas

Messages from webR’s supporting webr::canvas() graphics device are of the form,

{ type: 'canvas', data: { event: string, image?: ImageBitmap } }

The event property gives the name of the event type emitted from the graphics device. Plot image data is transmitted to the main thread in a 'canvasImage' event. Other types of event may be emitted, for example to signify that a new plot has begun.

Further information about plotting with webR can be found in the Plotting section.

Emscripten virtual filesystem

WebR runs under a virtual file system provided by Emscripten. Request messages can be sent from the main thread to interact with the virtual filesystem (VFS) through the WebRFS interface. This interface is designed to broadly match that of the Emscripten File System API.

Read a file on the VFS

Get the contents of a file from the Emscripten VFS using the WebR.FS.readFile() method. This method takes the full path name to a file in the form of a string as its argument and returns a JavaScript promise, resolving to the contents of the requested file provided in the form of a Uint8Array.

Write a file to the VFS

Write a new file to the Emscripten VFS using the WebR.FS.writeFile() method. In addition to the full path name as above, the content of the new file should be given as the method’s second argument in the form of a Uint8Array.

This method returns a JavaScript promise, resolving once the file has been created.

Other VFS operations

Further details for similar virtual filesystem operations, including functions for removing files and working with directories, can be found in the WebRFS interface reference.

Setting up an R REPL with Console

The Console class, in combination with JavaScript callback functions, can be used as an alternative way to use the R REPL without directly issuing messages to the webR communication channel.

The issuing of input messages and the consumption of output messages over the communication channel between the webR worker thread and the main thread is handled by Console, invoking the relevant callbacks provided.

The Console class constructor takes two arguments,

  • callbacks - A list of webR console callbacks to be used for this console.
  • options - The startup options to be provided to webR.

The Console.run() method is used to start the R console. Callback functions stdout and stderr are called with a single line of output as the first argument when the R REPL produces standard stream output. The default implementation of stdout and stderr writes to the console using console.log and console.error.

R code can be sent as input by calling the Console.stdin() method.

A long running R computation can be interrupted by calling the Console.interrupt() method.

The prompt callback function is called when webR produces a prompt at the REPL and is awaiting user input. The prompt character (usually > or +) is given as the first argument to the callback function. The default implementation of prompt shows a JavaScript prompt asking the user for input, and then sends the user input to stdin.

The canvasImage callback function is called when webR writes plots to the built-in HTML canvas graphics device. Once the default HTML canvas has been written to (e.g. by plotting something in R), the HTML canvas element can be accessed with the Console.canvas instance property.

See the Example Usage page to see a worked example of a HTML web page loading webR and starting a Console, creating an interactive R REPL directly in the page.