---
title: 'ThreadsafeFunction'
description: Call a JavaScript callback in other threads.
---

# ThreadsafeFunction

[`Threadsafe Function`](https://nodejs.org/api/n-api.html#asynchronous-thread-safe-function-calls) is a complex concept in Node.js. As we all know, Node.js is single-threaded, so you can't access [`napi_env`](https://nodejs.org/api/n-api.html#napi_env), [`napi_value`](https://nodejs.org/api/n-api.html#napi_value), and [`napi_ref`](https://nodejs.org/api/n-api.html#napi_ref) on another thread.

::: tip
[`napi_env`](https://nodejs.org/api/n-api.html#napi_env), [`napi_value`](https://nodejs.org/api/n-api.html#napi_value), and [`napi_ref`](https://nodejs.org/api/n-api.html#napi_ref)
are low level concepts in `Node-API`, which the `#[napi]` macro of **NAPI-RS**
is built on top of. **NAPI-RS** also provides a [low level
API](/docs/concepts/env) to access the original `Node-API`.

:::

`Node-API` provides complex `Threadsafe Function` APIs to call JavaScript functions on other threads. It's very complex, so many developers don't understand how to use it correctly. **NAPI-RS** provides a limited version of `Threadsafe Function` APIs to make it easier to use:

**lib.rs**

```rust {10}
use std::{sync::Arc, thread};

use napi::{
    bindgen_prelude::*,
    threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi]
pub fn call_threadsafe_function(callback: ThreadsafeFunction<u32, ()>) -> Result<()> {
  let tsfn = Arc::new(callback);
  for n in 0..100 {
    let tsfn = tsfn.clone();
    thread::spawn(move || {
      tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
    });
  }
  Ok(())
}
```

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.d.ts**

```ts
export function callThreadsafeFunction(
  callback: (err: null | Error, result: number) => void,
): void
```

## Return type

The return type of the `ThreadsafeFunction` is the same as the return type of the JavaScript callback, you can define the return type in the second generic parameter of `ThreadsafeFunction`:

**lib.rs**

```rust {10}
use std::thread;

use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode};
use napi_derive::napi;

#[napi]
pub fn call_threadsafe_function(callback: ThreadsafeFunction<u32, u32>) {
  thread::spawn(move || {
    callback.call_with_return_value(Ok(1), ThreadsafeFunctionCallMode::Blocking, |ret, _| {
      println!("ret: {:?}", ret); // Ok(101)
      Ok(())
    });
  });
}
```

**index.ts**

```ts
import { callThreadsafeFunction } from './index.js'

callThreadsafeFunction((err, result) => {
  return result + 100
})
```

## CallJsBackArgs

Sometimes the args passed to the `ThreadsafeFunction` are not the same as the args passed to the JavaScript callback. You can build the `ThreadsafeFunction` from `Function` with the `CallJsBackArgs` to achieve this:

**lib.rs**

```rust {17}
use std::thread;

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

struct Data {
  name: String,
}

#[napi]
pub fn call_threadsafe_function(callback: Function<String, ()>) -> Result<()> {
  let tsfn = callback
    .build_threadsafe_function()
    .build_callback(|ctx: ThreadsafeCallContext<Data>| Ok(format!("Hello {}", ctx.value.name)))?;
  thread::spawn(move || {
    tsfn.call(
      Data {
        name: "John".to_string(),
      },
      ThreadsafeFunctionCallMode::NonBlocking,
    );
  });
  Ok(())
}
```

::: warning
The callback argument and return types stored by a ThreadsafeFunction must be
`'static`, because the callback can run after the exported Rust function has
returned. Do not use scoped values such as `Unknown<'env>`, `Object<'env>`, or
`Function<'env, ...>` as `CallJsBackArgs`. Convert to owned Rust data such as
`String`, `Buffer`, or a plain owned struct before crossing the thread
boundary. An explicit scoped lifetime here produces `E0521` because the
borrowed JavaScript value would escape its callback scope; see
[napi-rs#3383](https://github.com/napi-rs/napi-rs/issues/3383).

:::

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.ts**

```ts {4}
import { callThreadsafeFunction } from './index.js'

callThreadsafeFunction((data) => {
  console.log(data) // Hello John
})
```

## Error Status

The error status of the `ThreadsafeFunction` is the same as the error status of the JavaScript callback. You can define the error status in the fourth generic parameter of `ThreadsafeFunction`:

**lib.rs**

```rust {25}
use std::{sync::Arc, thread};

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

pub struct CustomErrorStatus(String);

impl AsRef<str> for CustomErrorStatus {
  fn as_ref(&self) -> &str {
    &self.0
  }
}

impl From<Status> for CustomErrorStatus {
  fn from(value: Status) -> Self {
    CustomErrorStatus(value.to_string())
  }
}

#[napi]
pub fn call_threadsafe_function(
  tsfn: Arc<ThreadsafeFunction<u32, u32, u32, CustomErrorStatus>>,
) -> Result<()> {
  for n in 0..100 {
    let tsfn = tsfn.clone();
    thread::spawn(move || {
      tsfn.call(
        Err(Error::new(
          CustomErrorStatus("Custom".to_owned()),
          format!("Custom error: {}", n),
        )),
        ThreadsafeFunctionCallMode::Blocking,
      );
    });
  }
  Ok(())
}
```

## `CalleeHandled` error behavior

There are two different error-handling strategies for `Threadsafe Function`. The strategy can be defined in the fifth generic parameter of `ThreadsafeFunction`:

**lib.rs**

```rust
let tsfn: ThreadsafeFunction<u32, u32, u32, Status, false> = ...
```

### `CalleeHandled: true` (default behavior)

`Err` from Rust code will be passed into the first argument of the JavaScript callback. This behavior follows the async callback conventions from Node.js: https://nodejs.org/en/learn/asynchronous-work/javascript-asynchronous-programming-and-callbacks#handling-errors-in-callbacks. Many async APIs in Node.js are designed this way, like `fs.read`.

With `CalleeHandled: true`, you must call the `ThreadsafeFunction` with the `Result` type, so that the `Error` will be handled and passed back to the JavaScript callback:

**lib.rs**

```rust {11,16-22}
use std::{sync::Arc, thread};

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi]
pub fn call_threadsafe_function(
  tsfn: Arc<ThreadsafeFunction<u32, (), u32, Status, true>>,
) -> Result<()> {
  for n in 0..100 {
    let tsfn = tsfn.clone();
    thread::spawn(move || {
      tsfn.call(
        Err(Error::new(
          Status::GenericFailure,
          format!("Error with: {n}"),
        )),
        ThreadsafeFunctionCallMode::Blocking,
      );
    });
  }
  Ok(())
}
```

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.ts**

```ts {5}
import { callThreadsafeFunction } from './index.js'

callThreadsafeFunction((err, result) => {
  if (err) {
    console.error(err) // [Error: Error with: 0] { code: 'GenericFailure' }
  }
  console.log(result)
})
```

### `CalleeHandled: false`

No `Error` will be passed back to the JavaScript side. You can use this strategy to avoid the `Ok` wrapping on the Rust side if your code will never return `Err`.

With this strategy, `ThreadsafeFunction` doesn't need to be called with `Result<T>`, and the first argument of JavaScript callback is the value from the Rust, not `Error | null`.

::: warning
With the `CalleeHandled: false` strategy, the `ThreadsafeFunction` will not be
able to handle the error in the Rust threads, so you can't pass the `Error` back
to the JavaScript side.

The plain `call` method has no error channel back to Rust. A synchronous throw
in the JavaScript callback is routed through `napi_fatal_exception`, and a
returned `Promise` is not awaited automatically. If Rust needs the callback's
result, set a concrete `Return` type and use `call_async_catch`, or use
`call_with_return_value` and handle the `Result` passed to its completion
callback.

Use this mode only when native failures are handled before `call` and the
JavaScript callback cannot throw.

:::

**lib.rs**

```rust {11}
use std::{sync::Arc, thread};

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi]
pub fn call_threadsafe_function(
  tsfn: Arc<ThreadsafeFunction<u32, (), u32, Status, false>>,
) -> Result<()> {
  for n in 0..100 {
    let tsfn = tsfn.clone();
    thread::spawn(move || {
      tsfn.call(n, ThreadsafeFunctionCallMode::Blocking);
    });
  }
  Ok(())
}
```

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

**index.d.ts**

```ts {2}
export declare function callThreadsafeFunction(
  tsfn: (arg: number) => void,
): void
```

## `Weak` ThreadsafeFunction

By default, the `ThreadsafeFunction` will cause the event loop on the thread on which it is created to remain alive until the `ThreadsafeFunction` is destroyed. See [**Deciding whether to keep the process running**](https://nodejs.org/api/n-api.html#deciding-whether-to-keep-the-process-running).

If you don't want to keep the Node.js process/event loop alive, you can define the `Weak` parameter of `ThreadsafeFunction` to `true`:

**lib.rs**

```rust {11}
use std::{sync::Arc, thread};

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi]
pub fn call_threadsafe_function(
  tsfn: Arc<ThreadsafeFunction<u32, (), u32, Status, false, true>>,
) -> Result<()> {
  for n in 0..100 {
    let tsfn = tsfn.clone();
    thread::spawn(move || {
      tsfn.call(n, ThreadsafeFunctionCallMode::Blocking);
    });
  }
  Ok(())
}
```

If you call this function like this:

**index.ts**

```ts
import { callThreadsafeFunction } from './index.js'

// Weak mode does not keep the event loop alive by itself.
callThreadsafeFunction((n) => console.log(n))
```

If nothing else keeps the event loop alive, Node.js may exit before some or all queued callbacks run. Other active handles or work can keep the process alive long enough to deliver them. Weak mode does not guarantee callback delivery or suppress callbacks; it only removes this `ThreadsafeFunction` as a reason to keep the loop alive.

## `MaxQueueSize`

You can set the `MaxQueueSize` parameter of `ThreadsafeFunction` to limit the number of messages in the queue.

::: info
`MaxQueueSize` sets the queue capacity in both call modes. When that capacity is
reached, `Blocking` waits for space; `NonBlocking` returns immediately with
`Status::QueueFull`. See [`napi_call_threadsafe_function`](https://nodejs.org/api/n-api.html#napi_call_threadsafe_function) for more details.

:::

**lib.rs**

```rust {11,16}
use std::{sync::Arc, thread};

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi]
pub fn call_threadsafe_function(
  tsfn: Arc<ThreadsafeFunction<u32, (), u32, Status, false, false, 1>>,
) -> Result<()> {
  thread::spawn(move || {
    for n in 0..100 {
      let tsfn = tsfn.clone();
      let status = tsfn.call(n, ThreadsafeFunctionCallMode::NonBlocking);
      println!("{}", status)
    }
  });
  Ok(())
}
```

When you call this function, and add heavy work in the callback, you will see the `QueueFull` status return from the `tsfn.call`:

**index.ts**

```ts
import { callThreadsafeFunction } from './index.js'

function fib(n: number): number {
  if (n <= 1) return n
  return fib(n - 1) + fib(n - 2)
}

callThreadsafeFunction(() => {
  fib(40)
})
```

An illustrative run might produce output like this:

```
Ok
Ok
QueueFull
QueueFull
QueueFull
QueueFull
QueueFull
QueueFull
QueueFull
QueueFull
...
```

The exact number and order of `Ok` and `QueueFull` results depends on when the JavaScript thread drains the queue relative to the producer thread. A capacity of one guarantees the backpressure behavior, not a fixed output sequence.
