文档
概念
异步任务

异步任务

在讨论 AsyncTask 之前,我们需要先讨论 Task

Task

附加模块通常需要利用 libuv 中的异步助手作为其实现的一部分, 这样,它们就可以安排工作在异步执行,以便它们的方法可以在工作完成之前返回, 这样就可以避免阻塞 Node.js 应用程序的整体执行。

Task 特征提供了一种定义这样的异步任务的方法,该任务需要在 libuv 线程中运行,您可以实现 compute 方法,该方法将在 libuv 线程中调用。

lib.rs
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 Outputtype JsValueOutputcompute 方法的返回类型,JsValueresolve 方法的返回类型。

💡

我们需要分开 type Outputtype JsValue,因为我们无法在 fn compute 中回调 JavaScript 函数,它不在主线程上执行, 所以我们需要在主线程上运行的 fn resolve,根据 OutputEnv 创建 JsValue 并在 JavaScript 中回调它。

你可以使用底层 API Env::spawn 在 libuv 线程池中生成一个定义的 Task ,参见 引用 中的示例。

除了 computeresolve,您还可以提供 reject 方法,当 Task 遇到错误时,可以执行一些清理工作,例如 unref 一些对象:

lib.rs
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 方法,在 Taskresolvedrejected 后执行一些操作:

lib.rs
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) 来创建它。

lib.rs
#[napi]
fn async_fib(input: u32) -> AsyncTask<AsyncFib> {
  AsyncTask::new(AsyncFib { input })
}

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

index.d.ts
export function asyncFib(input: number) => Promise<number>

结合 AbortSignal 创建 AsyncTask

您可以给 AsyncTask 传入 AbortSignal ,这样如果 AsyncTask 还没有启动,您就可以中止它。

lib.rs
use napi::bindgen_prelude::AbortSignal;
 
#[napi]
fn async_fib(input: u32, signal: AbortSignal) -> AsyncTask<AsyncFib> {
  AsyncTask::with_signal(AsyncFib { input }, signal)
}

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

index.d.ts
export function asyncFib(input: number, signal: AbortSignal) => Promise<number>

如果您在 JavaScript 代码中调用 AbortController.abort,并且 AsyncTask 尚未启动, AsyncTask 将立即被中止,并 reject AbortError

test.mjs
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>

lib.rs
use napi::bindgen_prelude::AbortSignal;
 
#[napi]
fn async_fib(input: u32, signal: Option<AbortSignal>) -> AsyncTask<AsyncFib> {
  AsyncTask::with_optional_signal(AsyncFib { input }, signal)
}

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

index.d.ts
export function asyncFib(
  input: number,
  signal?: AbortSignal | undefined | null,
): Promise<number>
💡

如果 AsyncTask 已经启动或完成,AbortController.abort 将不会有任何效果。