
webR 0.2: Updates to webR's developer API | George Stagg | Posit
More from George Stagg on what’s new in the webR 0.2 release series, this time on webR’s developer API. If you’re a JavaScript or TypeScript developer, give webR a try and see what you might use it for! WebR makes it possible to run R code in the browser without the need for an R server to execute the code: the R interpreter runs directly on the user’s machine. It is possible to work entirely within webR and the R graphics devices, but you might also want to integrate webR into external JavaScript or TypeScript frameworks, including visualization packages. Learn about how to do this and more with webR’s developer API, including plotting data from webR using Observable JS, recent performance improvements, better error handling, worker event messages, and neatly terminating a webR session. 00:15 Performance improvements working with JavaScript 02:50 Example: Plotting data from webR using Observable JS 04:35 Type predicate functions and type conversion 07:30 Handling errors with webRError 09:15 Event messages from webr::canvas() 10:25 Safely handling webR termination 12:30 Links to documentation and resources Website and developer documentation: https://docs.r-wasm.org/ Examples of using webR with Observable JS: - Loading webR and R data manipulation: https://observablehq.com/d/6936259b898a25ce - Bitmap plotting and custom fonts: https://observablehq.com/d/32e2b7e465c0b994
image: thumbnail.jpg
Transcript#
This transcript was generated automatically and may contain errors.
Hello, in this video I'm going to be talking about updates to webR's developer API in webR 0.2.0.
So I want to jump straight in there and talk about some performance improvements that we've made while using webR with other JavaScript frameworks. So it is possible to work entirely within webR and return images directly from webR's graphics devices, but you might want to also integrate webR into external JavaScript frameworks, including visualization packages. So there's an example sort of workflow shown on the screen there that you might want to do. So you would, after initializing webR, you can imagine that you have some JavaScript data and you want to get that data into R so we can process it in some way. So webR has APIs to do that. Here, for example, that JavaScript data is being bound to a variable in the global environment in the running R process.
Once that's been done and that data is transferred to the R worker thread, you can evaluate some R code. Here, just the function doAnalysis is being evaluated, but this could really be anything in R. This could be something quite complicated, such as some kind of complicated data manipulation with the plier or some kind of sophisticated statistical analysis with tidy models. In any case, once you've done that computation, an R object will be returned to you and then you can convert that R object back into the JavaScript environment with this toJs method. That data is transferred from the R worker environment back to the JavaScript environment where you can use it in some way.
So if this is the kind of workflow that as a webR developer you'd be interested in, what we want to do is make sure that transferring of data from the JavaScript environment into R and back out of R into the JavaScript environment is as efficient and performant as possible. We've made some changes in webR 0.2.0 to support that. That transfer was originally done by encoding the data in a format called JSON. That's been replaced with something called the Message Pack protocol, which is binary-based instead of text-based and more efficient in other ways. Initial testing shows an order of magnitude performance improvement with this change, so it was well worth looking into, especially when working with large sets of JavaScript data.
Initial testing shows an order of magnitude performance improvement with this change, so it was well worth looking into, especially when working with large sets of JavaScript data.
Integrating webR with JavaScript visualization frameworks
If you are working in this way, an example now is shown on the screen of what you can do by moving data in and out of the R environment. Here, the start of this example installs an R package just to get some data. It's called the ParmaPenguins package. Then the next part of the example is basically just grabbing that data. So here we're evaluating some R code, but really we're just returning the Penguins data object. That's being converted into JavaScript and then transferred into a slightly different format. There's no real computational happening here on lines 8 to 16. It's just changing the shape of the data to better match data that is understood by the observable visualization framework, a JavaScript framework.
If you have used observable before, you probably recognize these next lines. They should be fairly familiar. They're the standard way to draw a dot plot in observable. That can now take that data that came from R that could have been modified and computed on in a sophisticated way, and then plotted in the JavaScript native environment. When you do that, this is what you get. A plot that looks like this, produced by observable, but with the data itself coming from R. I like this example because it demonstrates one of the real benefits to web technologies in general, which is interconnectivity. It's very easy to flexibly connect different projects together when you're working on the web. I think this is a good example of that when it comes to joining webR as part of a wider JavaScript project.
TypeScript type safety
Another change we've made is for TypeScript users. When you're working in TypeScript, it's really important to pay attention to the types of objects. That's true of objects that are coming from R too. In this example, you can see that we're just evaluating this string here, which produces an R double object. Then we're running a method toNumber, which exists on a webR double object, and just converts that number into a JavaScript number. Converts the double into a JavaScript number. This is actually an error under TypeScript. The reasoning for that is that evalR, the function that evaluates R code, could evaluate anything. It doesn't have to return a double, it could return a character, it could return a logical vector, it could return pretty much anything. There's no guarantee that this toNumber method exists in the webR API to be used on this R object. In fact, evalR is just typed to return an R object. That means it really could be any kind of fundamental R data type. There's a couple of ways to handle this.
Before current release of webR, the way we were using was a TypeScript type assertion feature using this add keyword. Basically, we just take the result and we say, okay, this object is definitely a double. Therefore, TypeScript is able to know that you can use the toNumber method on it, which again, exists in the webR API. That works, but it has a problem. It means that you need to know for definite what type of object is returned by evalR. You need to know exactly what R object is being returned. Otherwise, this is an assertion, so it could be wrong. I could be returning a number here, but setting it as an R character, and that would cause issues further down the line.
webR ships with type predicate functions, and here is an example of one of them, isRDouble, and similar functions exist for other fundamental R object types, isRCharacter, isRLogical. What that allows you to do is create a branch where the type of that object is checked by isRDouble. If the R object is a double, that function returns true. Because that function returns true only in that case, TypeScript is able to deduce that inside this branch, the object variable is definitely an R double, and therefore, it is perfectly fine to use that toNumber method that exists on an R double. This is quite nice because it means that you can return different types of object with evalR. It doesn't have to just be one particular type of R object, and you could have different branches and different if statements to handle different types of object.
Handling errors from R
The next thing I'd like to talk about is handling errors. One of the issues that we had with the previous version of webR is that if you have some R code that causes an error condition, the evalR function automatically converts that into a JavaScript error, which is nice because it means that you can handle errors in R code in the native JavaScript environment. The issue with that is if you have some kind of code like the following, where you evaluate some R code and then do some JavaScript work on the result, and an error is thrown, it's not entirely clear where that error came from. It could have come from inside R, or it could come from deep inside this JavaScript function. There were ways to handle that. You could parse the message to work out exactly what the error means, or you could do other things, but they're not particularly elegant.
One of the changes we've made is that errors from R now are thrown as this webR error, as an instance of this webR error class that exists in the package. That means that you can check using the instance of a keyword in JavaScript to see, is this object a webR error, or is it just some other JavaScript error? Then you can handle that error as you like, based on whether it came from some R code, or it came from your JavaScript code. It's just a nice, easy way to be able to check exactly where your error condition came from.
Canvas messaging and session termination
We've made changes to the webR canvas messaging system. As I said in the first video, we've upgraded the webR canvas to have a lot more support for different features, including drawing canvas graphics on the worker thread, and then displaying them on the main thread in a different way, using off-screen canvas. Due to those changes, we've redesigned the messaging system for the canvas device. There are now a few different messages. This message, canvas new page, indicates that a new plot has been created. Previously, it was not clear when a new plot had been created in webR when using the canvas graphics device. The canvas image event indicates that there's some image data ready to be shown on the main thread. The message looks like this, and the image property that's returned in that message has a JavaScript image bitmap object. That object can be written to a canvas element on the main thread. That message will indicate that a plot has been made, it's finished rendering, and it's ready to be shown.
Another change we've made is how webR is terminated. The following example is a useful pattern when you're using webR to be able to continuously handle webR output. Here, for example, the read function is being used to wait for some output from R. If you have some long-running computation that will produce output after a while, it will wait for that output. It will wait for one of those output messages. Once the message has been received, it will be handled in some way. Here, we have a switch statement to handle different types of message. Then, once that message has been handled, the loop wraps around, and it waits for the next message. This loop is an infinite loop, so it will continue on indefinitely, continuously handling messages from webR.
Now, a question arises. It's possible to terminate a webR session using this close function. That will close the worker thread, terminate the thread, and stop any further R processing. If you do that, a question comes up of, okay, well, inside the infinite loop, how do I know when to stop? That loop will just sit there waiting for a message that may never be received or will never be received. As part of the changes in the new version of webR, when webR is terminated using this close function, a message of type close is emitted. The very last thing it does is emit a message of a type close. That means you can add just a single case statement in your switch here to exit your infinitely running loop when that closed statement is received. That means that once the webR thread terminates and that message is being received, you don't have that function sitting there waiting for a message that will never be received. It helps you clean up after yourself when using webR.
That's everything I want to talk about in this video. If you are interested in the webR API, the documentation lists a lot more than I could fit in a single video. Do go and read that. You can install webR from NPM or you can import it from CDN. If you do use webR in your own applications, please do let me know if you have any exciting features to show me. My email is just shown on the screen there and I'd love to see what you do with webR. Thank you.

