Skip to main content
Version: Unreleased

Concurrency model

To understand the concurrency model of the Worklets library and why it's needed, we have to look at how a React Native app is structured.

The React Native model​

A React Native app is, first and foremost, a native app. Like every native app, it has a main thread — usually called the User Interface (UI) thread — that receives touches, lays out views and draws them on screen.

Your JavaScript doesn't run there. At startup, the native app creates two things: a separate thread, the JavaScript thread (JS thread), and a Hermes JavaScript Virtual Machine (JSVM), also called a JavaScript Runtime. We'll call this Runtime the React Native Runtime. Then, it delegates the execution of your JavaScript Bundle and its event loop to the JS thread.

The JS thread executes all of your app's JavaScript code — your React components, your business logic, your event handlers. Its job is to figure out what the screen should look like and tell the UI thread how to render it.

This split is what the official docs call the threading model.

JS thread
RN Runtimeyour JavaScript<View> <Text>{count}</Text> </View>
runs components and handlers, then prepares UI updates
Common layer
UI updatescreate <Text>set text "1"layout <View>
native structures both threads can access
UI thread
Native UIconsumes the updates
draws the views on screen
note

A JavaScript Runtime and a thread aren't the same thing!

  • A JavaScript Runtime is an execution environment: a set of native functions (CPU instructions) to evaluate JavaScript code and data structures to store global objects, allocated JavaScript values, and the current execution state.

  • A thread is an OS-level primitive for executing CPU instructions. The operating system decides when and on which CPU core to run the thread.

A JavaScript Runtime is an exclusive resource. A thread must acquire it before using it to execute JavaScript code. After the thread finishes execution and releases it, another thread can acquire it.

Limitations​

Since all your app logic lives inside the JavaScript Bundle and is executed only on one thread, you might run into a situation where a critical UI event has to wait for the JS thread to trigger the respective JavaScript event handler, which makes the app visibly lag.

UI thread
taptaptaptaptaptap
JS thread
onTaponTaponTap ×3busyTask

A similar situation happens if animation calculations are done with useState (please don't do this in real apps!) on a busy JS thread:

JS thread
busyTask
UI thread
JS frame
UI frame

Overcoming limitations​

To solve this issue, we have to somehow move the execution of the UI-critical parts of our app off the JS thread. We can't use a native module if we want to keep our logic purely in JavaScript code. We can't forcefully run some JavaScript code on the UI thread concurrently with the RN Runtime. JavaScript Runtimes are single-threaded by design, and doing so would result in an immediate crash.

This means that we have to create a new Runtime and move the event and animation logic outside the RN Runtime to the new Runtime.

UI thread
taptaptaptaptaptap
JS thread
onTapbusyTaskonTap ×3onTap
RN Runtime
new Runtime

Now, when the event comes, we can synchronously handle it on the UI thread, using our new Runtime. This is exactly the UI Runtime that the Worklets library exposes.

UI thread
taptaptaptaptaptaponTaponTaponTaponTaponTap
JS thread
busyTask
RN Runtime
UI Runtime

Moreover, we can also offload the heavy computation that kept our JS thread overloaded. It's better not to delegate that work to the UI thread as it would make the app unresponsive. We don't want to preempt the UI Runtime from another thread either — that could block the UI thread from handling the touch event, as it has to wait for the UI Runtime to be released.

What we can do is spawn another Runtime and a thread. We'll dedicate that thread to executing tasks using the new Runtime. We'll call this Runtime a Worker Runtime.

UI thread
taptaptaptaptaptap
JS thread
onTaponTaponTaponTaponTap
Worker thread
busyTask
RN Runtime
Worker Runtime

This is how the Worklets library allows you to overcome bottlenecks on the RN Runtime and the JS thread, while keeping your app's logic strictly in JavaScript.

Delegating JavaScript​

There's one last bit of the puzzle missing — it's not that simple to just send some JavaScript functions from one Runtime to be executed on another Runtime. The single-threaded design of JavaScript Runtimes makes casual JavaScript functions closely tied to a given Runtime instance and not suitable for any kind of serialization. For that purpose, we need some special functions.

Worklet functions​

A worklet function (also called just a worklet) is a JavaScript function that can be safely moved and dispatched across JavaScript Runtimes within a React Native app.

To turn a casual JavaScript function into a worklet function, all you need to do is add the 'worklet' directive at the top of your function.

function myWorklet() {
'worklet';
console.log('I can be safely sent to any Runtime!');
}

Functions marked with the directive are transformed with the Worklets plugin, in a process we call workletization, into special entities that are safe to be juggled between JavaScript Runtimes.

It's time to learn how to use these worklet functions — in the next chapter.