异步任务
在讨论 AsyncTask
之前,我们需要先讨论 Task
。
Task
附加模块通常需要利用 libuv 中的异步助手作为其实现的一部分, 这样,它们就可以安排工作在异步执行,以便它们的方法可以在工作完成之前返回, 这样就可以避免阻塞 Node.js 应用程序的整体执行。
Task
特征提供了一种定义这样的异步任务的方法,该任务需要在 libuv 线程中运行,您可以实现 compute
方法,该方法将在 libuv 线程中调用。
use napi::{Task, Env, Result, JsNumber};
struct AsyncFib {
input: u32,
}
impl Task for AsyncFib {
type Output = u32;
type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> {
Ok(fib(self.input))
}
fn resolve(&mut self, env: Env, output: u32) -> Result<Self::JsValue> {
env.create_uint32(output)
}
}
fn compute
在 libuv 线程中运行,您可以在这里运行一些繁重的计算,这不会阻塞 JavaScript 主线程。
你可能会注意到 Task
特征上有两个关联类型,type Output
和 type JsValue
,
Output
是 compute
方法的返回类型,JsValue
是 resolve
方法的返回类型。
我们需要分开 type Output
和 type JsValue
,因为我们无法在 fn compute
中回调 JavaScript 函数,它不在主线程上执行, 所以我们需要在主线程上运行的 fn resolve
,根据 Output
和 Env
创建 JsValue
并在 JavaScript 中回调它。
你可以使用底层 API Env::spawn
在 libuv 线程池中生成一个定义的 Task
,参见 引用 中的示例。
除了 compute
和 resolve
,您还可以提供 reject
方法,当 Task
遇到错误时,可以执行一些清理工作,例如 unref
一些对象:
struct CountBufferLength {
data: Ref<JsBufferValue>,
}
impl CountBufferLength {
pub fn new(data: Ref<JsBufferValue>) -> Self {
Self { data }
}
}
impl Task for CountBufferLength {
type Output = usize;
type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> {
if self.data.len() == 10 {
return Err(Error::from_reason("len can't be 10".to_string()));
}
Ok((&self.data).len())
}
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
self.data.unref(env)?;
env.create_uint32(output as _)
}
fn reject(&mut self, env: Env, err: Error) -> Result<Self::JsValue> {
self.data.unref(env)?;
Err(err)
}
}
您还可以提供一个 finally
方法,在 Task
被 resolved
或 rejected
后执行一些操作:
struct CountBufferLength {
data: Ref<JsBufferValue>,
}
impl CountBufferLength {
pub fn new(data: Ref<JsBufferValue>) -> Self {
Self { data }
}
}
#[napi]
impl Task for CountBufferLength {
type Output = usize;
type JsValue = JsNumber;
fn compute(&mut self) -> Result<Self::Output> {
if self.data.len() == 10 {
return Err(Error::from_reason("len can't be 5".to_string()));
}
Ok((&self.data).len())
}
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
env.create_uint32(output as _)
}
fn finally(&mut self, env: Env) -> Result<()> {
self.data.unref(env)?;
Ok(())
}
}
impl Task for AsyncFib
上面的 #[napi]
宏只是为了生成 .d.ts
文件,
如果这里没有定义 #[napi]
,生成的 TypeScript 类型里, AsyncTask
的返回值类型将是 Promise<unknown>
。
AsyncTask
你定义的 Task
不能直接返回给 JavaScript,JavaScript 引擎不知道如何运行和解析你的 struct
的值,
AsyncTask
是可以返回给 JavaScript 引擎的 Task
的包装,
可以使用 Task
和可选的 AbortSignal
(opens in a new tab) 来创建它。
#[napi]
fn async_fib(input: u32) -> AsyncTask<AsyncFib> {
AsyncTask::new(AsyncFib { input })
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export function asyncFib(input: number) => Promise<number>
结合 AbortSignal
创建 AsyncTask
您可以给 AsyncTask
传入 AbortSignal
,这样如果 AsyncTask
还没有启动,您就可以中止它。
use napi::bindgen_prelude::AbortSignal;
#[napi]
fn async_fib(input: u32, signal: AbortSignal) -> AsyncTask<AsyncFib> {
AsyncTask::with_signal(AsyncFib { input }, signal)
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export function asyncFib(input: number, signal: AbortSignal) => Promise<number>
如果您在 JavaScript 代码中调用 AbortController.abort
,并且 AsyncTask
尚未启动,
AsyncTask
将立即被中止,并 reject AbortError
。
import { asyncFib } from './index.js'
const controller = new AbortController()
asyncFib(20, controller.signal).catch((e) => {
console.error(e) // Error: AbortError
})
controller.abort()
如果您不知道 AsyncTask
是否需要中止,
您还可以给 AsyncTask
传入 Option<AbortSignal>
:
use napi::bindgen_prelude::AbortSignal;
#[napi]
fn async_fib(input: u32, signal: Option<AbortSignal>) -> AsyncTask<AsyncFib> {
AsyncTask::with_optional_signal(AsyncFib { input }, signal)
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export function asyncFib(
input: number,
signal?: AbortSignal | undefined | null,
): Promise<number>
如果 AsyncTask
已经启动或完成,AbortController.abort
将不会有任何效果。