ThreadsafeFunction
ThreadSafe Function (opens in a new tab) é um conceito complexo no Node.js. Como todos sabemos, o Node.js é single-threaded, então você não pode acessar napi_env (opens in a new tab), napi_value (opens in a new tab), e napi_ref (opens in a new tab) em outra thread.
napi_env (opens in a new tab),
napi_value (opens in a new tab), e
napi_ref (opens in a new tab) são conceitos de
baixo nível em Node-API, na qual a macro #[napi] do NAPI-RS é
construída em cima. NAPI-RS também fornece uma API de baixo
nível para acessar a Node-API original.
Node-API fornece APIs complexas de Threadsafe Function para chamar funções JavaScript em outras threads. É muito complexo, então muitos desenvolvedores não entendem como usá-lo corretamente. O NAPI-RS fornece uma versão limitada das APIs de Threadsafe Function para facilitar o uso:
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
export function callThreadsafeFunction(callback: (...args: any[]) => any): voidThreadsafeFunction é muito complexa, então o NAPI-RS não fornece a geração precisa de definição TypeScript para ela. Se você deseja ter um tipo TypeScript melhor, pode usar #[napi(ts_args_type)] para sobreescrever o tipo do argumento JsFunction:
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi(ts_args_type = "callback: (err: null | Error, result: number) => void")]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
export function callThreadsafeFunction(
callback: (err: null | Error, result: number) => void,
): voidErrorStrategy
Existem duas estratégias diferentes de tratamento de erros para Threadsafe Function. A estratégia pode ser definida no segundo parâmetro genérico de ThreadsafeFunction:
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = ...O primeiro argumento no parâmetro genérico é o tipo de retorno da Threadsafe Function.
ErrorStrategy::CalleeHandled
O Err do código Rust será passado como o primeiro argumento para a função de retorno de chamada(callback) JavaScript. Esse comportamento segue as convenções de retorno de chamada assíncrona do Node.js: https://nodejs.org/en/learn/asynchronous-work/javascript-asynchronous-programming-and-callbacks#handling-errors-in-callbacks (opens in a new tab). Muitas APIs assíncronas no Node.js são projetadas nesse formato, como fs.read.
Com ErrorStrategy::CalleeHandled, você deve chamar a ThreadsafeFunction com o tipo Result, para que o Error seja tratado e retornado para a função de callback JavaScript:
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi(ts_args_type = "callback: (err: null | Error, result: number) => void")]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}ErrorStrategy::Fatal
Nenhum Error será retornado para o lado JavaScript. Você pode usar essa estratégia para evitar o encapsulamento Ok no lado Rust se seu código nunca retornar Err.
Com essa estratégia, ThreadsafeFunction não precisa ser chamada com Result<T>, e o primeiro argumento do callback JavaScript é o valor vindo do Rust, não Error | null.
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi(ts_args_type = "callback: (result: number) => void")]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::Fatal> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(n, ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
export function callThreadsafeFunction(callback: (result: number) => void): void