异步任务
在讨论 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 将不会有任何效果。