Skip to content

异步函数

TIP

为了使用 async fn,你必须开启 napiasynctokio_rt 特性:

Cargo.toml
toml
[dependencies]
napi = { version = "3", features = ["async", "tokio_fs", "tokio_time"] } 
napi-derive = "3"

下面的例子使用了 tokio_fstokio_time 子特性。只启用 addon 实际使用的 Tokio API。

Tokio 集成

你可以通过 AsyncTaskThreadsafeFunction 完成很多异步/多线程工作,但有时你可能想直接使用 Rust 异步生态系统中的 crate。

启用 asynctokio_rt 后,NAPI-RS 会提供一个 Tokio 运行时。如果你在导出的 async fnawait 一个 Tokio future,NAPI-RS 会在该运行时上执行它, 并把结果转换为 JavaScript Promise

lib.rs
rust
use napi::bindgen_prelude::*;
use napi_derive::napi;
use napi::tokio::fs;

#[napi]
pub async fn read_file_async(path: String) -> Result<Buffer> { 
  let content = fs::read(path).await?;
  Ok(content.into())
}

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

index.d.ts
ts
export function readFileAsync(path: string): Promise<Buffer>

不安全的 &mut self

在某些情况下,你可能需要在 async fn 中使用 &mut self。然而在 NAPI-RS 中这是 unsafe 的,因为 self 同时也被 Node.js 运行时持有。你无法确保 self 只被 Rust 持有。

lib.rs
rust
use napi_derive::napi;

#[napi]
pub struct Engine {}

#[napi]
impl Engine {
  #[napi]
  pub async fn run(&mut self) {} 
}
rust
error: &mut self in async napi methods should be marked as unsafe
 --> src/lib.rs:9:18
  |
9 |     pub async fn run(&mut self) {}
  |                  ^^^

要在 async fn 中使用 &mut self,你需要把该 fn 标记为 unsafe

lib.rs
rust
use napi_derive::napi;

#[napi]
pub struct Engine {}

#[napi]
impl Engine {
  #[napi]
  pub async unsafe fn run(&mut self) {} 
}

自动引用

通常,JavaScript 值只在一次函数调用内有效。async fn 则不同——JavaScript 值可能在任何一个 await 点被垃圾回收。

INFO

更多细节参见理解生命周期

有 3 种参数会被自动转换为 Reference 类型:

  • &self
  • &mut self
  • This<T>

考虑下面的例子:

lib.rs
rust
use napi::bindgen_prelude::*;
use napi_derive::napi;

#[napi]
pub struct NativeClass {
  name: String,
}

#[napi]
impl NativeClass {
  #[napi(constructor)]
  pub fn new(name: String) -> Self {
    Self { name }
  }

  #[napi]
  pub async fn sleep(&self, delay: u32) -> Result<&str> {
    napi::tokio::time::sleep(std::time::Duration::new(delay as u64, 0)).await;
    Ok(&self.name)
  }
}
index.ts
ts
const nativeClass = new NativeClass('Brooklyn')

const name = await nativeClass.sleep(1)

console.log(name) // Brooklyn

async fn 调用之前,NAPI-RS 会对持有 NativeClass 的 JavaScript Object 值隐式调用一次 napi_create_reference;在 async fn 调用结束之后,再隐式调用一次 napi_delete_reference

这一策略确保 NativeClassasync fn 调用期间保持存活。

async fn 之外:AsyncBlock

导出的 async fn 覆盖了常见场景,但它总是用函数的返回值来 resolve 它的 promise——即在 future 完成之后进行转换。当你需要更多控制时——例如用一个只能在 JavaScript 线程上创建的值来 resolve(比如零拷贝的 BufferSlice<'static> 或 Web Response),或者在 future 完成(settle)时通过 dispose 钩子执行清理——可以改为在一个同步的 #[napi] 函数中返回 AsyncBlock<T>。future 会在 NAPI-RS 运行时上立即(eagerly)启动,并像 async fn 一样转换为 JavaScript Promise

完整的 AsyncBlockBuilder API 和示例参见 Web Streams:AsyncBlock

自定义运行时

默认情况下,future 运行在 NAPI-RS 管理的 Tokio 运行时上。启用 async-runtime Cargo 特性后,你可以改为注册自己的执行器——包括面向无线程 WASI 或 workerd 的、不依赖 tokio 的运行时——之后每个生成的 async fn 都会运行在它上面。参见自定义异步运行时

最后更新于
LongYinan