Announcement

👇Official Account👇

Welcome to join the group & private message

Article first/tail QR code

Skip to content

WASI 0.3 原生异步深度解析:WebAssembly 从回调地狱到 async/await 的范式跃迁

从 WASI 0.2 的痛点说起

WASI 0.2 把 Component Model 带到了 WebAssembly——模块可以组合、接口可以跨语言声明、类型系统从数值类型升级到 record/variant/list。2026 年的 Wasm 生态已经围绕 0.2 建立起来:wasmCloud、Fermyon Spin、SpinKube 都在生产中运行。

但所有用过 WASI 0.2 写服务端代码的人都踩过同一个坑:异步 IO 靠回调

rust
// WASI 0.2 的"回调地狱"——每个 IO 操作都是 callback
fn fetch_url(url: &str, callback: Box<dyn Fn(Result<String, Error>)>) {
    let request = wasi_http::outgoing_handler::Request::new(
        Method::Get,
        url.parse().unwrap()
    );
    wasi_http::outgoing_handler::send_request(
        request,
        |response| {
            response.body(
                |body| {
                    body.read_to_end(
                        |data| {
                            callback(Ok(String::from_utf8_lossy(&data).to_string()))
                        }
                    )
                }
            )
        }
    );
}

这段代码只有三层嵌套。真实项目中的 IO pipeline 轻松五层起步——读取请求 → 解析 → 查询数据库 → 调用上游 API → 写回响应。每一层都是回调嵌套,错误处理散落在各级 callback 中,完全丧失了 Rust async/await 的线性代码可读性。

WASI 0.3 解决的就是这个问题。

WASI 0.3 的核心:原生 async/await

事件循环内建到 Component Model

WASI 0.3 不是在 WASI 0.2 上"加了个 async wrapper"——它把异步语义直接内建到 Component Model 的核心定义中。这意味着:

  1. Component 接口可以用 async 声明函数——调用方和被调方都理解这是异步操作
  2. 运行时负责事件循环——不需要手写 event loop 或 callback 链
  3. task 可以被调度、暂停、恢复、取消——一等公民的异步单元
wit
// WASI 0.3 WIT 接口定义:async 函数
package friday:api;

interface database {
    // 同步函数(不变)
    get-user: func(id: u64) -> user;

    // WASI 0.3 新增:async 函数
    async get-user-async: func(id: u64) -> user;

    // async 流式返回
    async stream-events: func(filter: string) -> list<event>;
}

Rust 中的 WASI 0.3 async

rust
// WASI 0.3 的 async/await——终于线性了
async fn fetch_url(url: &str) -> Result<String, Error> {
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    Ok(body)
}

// 并发请求——就像在普通 Rust 异步运行时中一样
async fn fetch_multiple(urls: Vec<&str>) -> Vec<Result<String, Error>> {
    let futures: Vec<_> = urls.iter().map(|url| fetch_url(url)).collect();
    futures::future::join_all(futures).await
}

代码看起来和标准的 Rust async 没有区别——这是 WASI 0.3 的核心设计目标:让 Wasm 目标上的异步代码与原生平台上的异步代码保持一致的编程模型

技术架构:WASI 0.3 异步模型

三层异步栈

┌─────────────────────────────────────────────────────────┐
│              应用代码层(Rust / Go / Python)             │
│  async fn handle_request(req: Request) -> Response      │
│    let data = db.query("...").await?;                   │
│    let resp = http.post("...", data).await?;             │
│    Ok(Response::from(resp))                             │
├─────────────────────────────────────────────────────────┤
│              Component Model Async 层                    │
│  ┌───────────────┐  ┌───────────────┐  ┌─────────────┐ │
│  │ async task    │  │ task scheduler│  │ resource     │ │
│  │ creation      │  │ (event loop)  │  │ waiter       │ │
│  ├───────────────┤  ├───────────────┤  ├─────────────┤ │
│  │ task.state     │  │ ready queue   │  │ subscribe   │ │
│  │ task.context   │  │ suspend/resume│  │ poll/event  │ │
│  │ task.canceller │  │ work-stealing │  │ drop/cancel │ │
│  └───────────────┘  └───────────────┘  └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│              WASI Preview 3 接口层                       │
│  wasi:io/async-input-stream  wasi:io/async-output-stream│
│  wasi:http/types  wasi:clocks  wasi:filesystem/types    │
├─────────────────────────────────────────────────────────┤
│              运行时实现层(Wasmtime 37+)                 │
│  ┌────────────┐  ┌────────────┐  ┌──────────────────┐ │
│  │ Component  │  │ Async      │  │ Platform         │ │
│  │ Loader     │  │ Runtime    │  │ I/O (epoll/kqueue│ │
│  │ + Linker   │  │ (tokio-like)│  │ / io_uring)      │ │
│  └────────────┘  └────────────┘  └──────────────────┘ │
└─────────────────────────────────────────────────────────┘

async task 的生命周期

Task Created ──→ Pending ──→ Ready ──→ Running ──→ Completed
     │              │           │           │            │
     │              │           │           │            ▼
     │              │           │      Suspended    Task Result
     │              │           │           │      delivered to
     │              │           │           │      awaiter
     │              │           └───────────┘
     │              │     (resource ready)
     │              ▼
     │         (waiting for
     │          resource/event)
     │              │
     ▼              ▼
  Cancelled ←──── Cancel signal

与 WASI 0.2 的对比

特性WASI 0.2WASI 0.3
异步 IO回调函数(async-lower原生 async/await
代码风格callback nesting线性 async/await
错误处理逐级回调传播? 运算符 + Result
并发手动管理回调join_all / select!
取消无标准机制task cancellation 一等公民
资源管理手动 close callbackasync Drop / RAII
运行时不提供事件循环内建事件循环

Wasmtime 37+ 实现

运行时架构

Wasmtime 37 是首个完整支持 WASI 0.3 的生产级运行时。它将 Component Model 的 async task 映射到运行时内部的调度器:

rust
// Wasmtime 37+ 配置 WASI 0.3
use wasmtime::component::{Component, Linker};
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::WasiCtxBuilder;

fn setup_wasmtime_03() -> (Engine, Store<WasiCtx>) {
    let mut config = Config::new();
    // 启用 Component Model
    config.wasm_component_model(true);
    // 启用 WASI 0.3 原生异步
    config.wasi_async(true);
    // 启用并行调度器
    config.async_stack_size(1024 * 1024);  // 1 MB per async task

    let engine = Engine::new(&config).unwrap();
    let ctx = WasiCtxBuilder::new()
        .inherit_stdout()
        .inherit_stderr()
        .build();
    let store = Store::new(&engine, ctx);

    (engine, store)
}

加载和调用 async component

rust
// 定义 async WIT 接口的 Rust binding
wasmtime::component::bindgen!({
    path: "api.wit",
    world: "api-world",
    async: true,  // ← 关键:生成 async 函数
});

use api::api::database::Database;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (engine, mut store) = setup_wasmtime_03();

    // 加载 component
    let component = Component::from_file(&engine, "db_server.wasm")?;

    // 链接 WASI 接口
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?;

    // 实例化
    let (bindings, _) = ApiWorld::instantiate_async(
        &mut store, &linker, &component
    ).await?;

    // 调用 async 函数——就像普通 Rust async 一样
    let user = bindings.database()
        .call_get_user_async(&mut store, 42)
        .await?;

    println!("User: {:?}", user);

    // 并发调用
    let futures = vec![
        bindings.database().call_get_user_async(&mut store, 1),
        bindings.database().call_get_user_async(&mut store, 2),
        bindings.database().call_get_user_async(&mut store, 3),
    ];
    let results = futures::future::join_all(futures).await;

    Ok(())
}

服务端 Wasm 微服务架构

模式一:异步 HTTP 代理

rust
// 一个完整的 WASI 0.3 HTTP 代理微服务
use wasi::http::{outgoing_handler, types::OutgoingRequest};

#[derive(Debug)]
struct ProxyConfig {
    upstream: String,
    timeout_ms: u64,
}

async fn handle_request(
    req: IncomingRequest,
    config: ProxyConfig,
) -> Result<OutgoingResponse, ProxyError> {
    // 1. 异步读取请求体
    let body = req.body().read_to_end().await?;

    // 2. 构造上游请求
    let upstream_req = OutgoingRequest::new(req.method());
    upstream_req.set_path(&req.path_with_query());

    // 3. 异步发送到上游
    let upstream_resp = outgoing_handler::handle(upstream_req)
        .await?;

    // 4. 异步读取上游响应
    let upstream_body = upstream_resp.body()
        .read_to_end().await?;

    // 5. 构造响应
    let resp = OutgoingResponse::new(200);
    resp.body().write_all(&upstream_body).await?;

    Ok(resp)
}

// WASI 0.3 入口——运行时调用
#[export_name = "handle"]
pub async fn handle(req: IncomingRequest) -> OutgoingResponse {
    let config = ProxyConfig {
        upstream: std::env::var("UPSTREAM").unwrap(),
        timeout_ms: 5000,
    };
    match handle_request(req, config).await {
        Ok(resp) => resp,
        Err(e) => {
            let resp = OutgoingResponse::new(502);
            resp.body()
                .write_all(format!("Proxy error: {e}").as_bytes())
                .await
                .unwrap();
            resp
        }
    }
}

模式二:多组件协作

┌──────────────────────────────────────────────────────────┐
│                Wasmtime 37+ Runtime                       │
│                                                          │
│  ┌─────────────────────────────────────────────────────┐ │
│  │              Event Loop (async scheduler)           │ │
│  │  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐          │ │
│  │  │task1│ │task2│ │task3│ │task4│ │task5│          │ │
│  │  │HTTP │ │DB   │ │Cache│ │Auth │ │Log  │          │ │
│  │  │proxy│ │query│ │read │ │check│ │write│          │ │
│  │  └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘          │ │
│  │     │       │       │       │       │               │ │
│  └─────┼───────┼───────┼───────┼───────┼──────────────┘ │
│        │       │       │       │       │                 │
│  ┌─────▼───────▼───────▼───────▼───────▼─────────────┐  │
│  │          WASI 0.3 Component Interface            │  │
│  │  wasi:http  wasi:io  wasi:clocks  wasi:filesystem │  │
│  └───────────────────────────────────────────────────┘  │
│        │           │           │           │             │
│  ┌─────▼──┐  ┌─────▼──┐  ┌─────▼──┐  ┌─────▼──┐       │
│  │Postgres│  │Redis   │  │Timer   │  │File    │       │
│  │socket  │  │socket  │  │fd      │  │system  │       │
│  └────────┘  └────────┘  └────────┘  └────────┘       │
│                                                          │
│  ┌─────────────────────────────────────────────────────┐ │
│  │          Platform I/O Layer                        │  │
│  │  epoll (Linux) / kqueue (macOS) / io_uring         │  │
│  └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

每个组件是一个独立的 Wasm Component,通过 WIT 接口声明 async 函数。运行时的调度器负责在组件间传递异步结果。

模式三:跨语言组件

wit
// database.wit——数据库组件接口
package friday:db;

interface database {
    record user {
        id: u64,
        name: string,
        email: string,
    }

    // Rust 实现的 async 函数
    async query: func(sql: string, params: list<string>) -> list<user>;
    async execute: func(sql: string, params: list<string>) -> u64;
}

// cache.wit——缓存组件接口
package friday:cache;

interface cache {
    // Python 实现的 async 函数
    async get: func(key: string) -> option<string>;
    async set: func(key: string, value: string, ttl: u64) -> bool;
}
rust
// Go 组件调用 Rust 和 Python 的 async 组件
// 通过 WIT 统一接口,跨语言 async 调用

async fn get_user_cached(id: u64) -> User {
    // 先查缓存(Python 组件)
    let cache_key = format!("user:{}", id);
    if let Some(cached) = cache.get(&cache_key).await {
        return serde_json::from_str(&cached).unwrap();
    }

    // 查数据库(Rust 组件)
    let users = db.query(
        "SELECT * FROM users WHERE id = $1",
        vec![id.to_string()]
    ).await;

    // 写缓存
    if let Some(user) = users.first() {
        cache.set(
            &cache_key,
            &serde_json::to_string(user).unwrap(),
            300  // 5 分钟 TTL
        ).await;
    }

    users.into_iter().next().unwrap()
}

从 WASI 0.2 迁移到 0.3

迁移步骤

toml
# Cargo.toml
[dependencies]
# 升级 wasm 绑定到 0.3
wasi = { version = "0.3.0", features = ["async"] }
# 升级 wasmtime 到 37+
wasmtime = { version = "37", features = ["component-model", "async"] }
# build target 更新
# WASI 0.2
wasm32-wasip1  → wasm32-wasip2  (Component Model, sync only)

# WASI 0.3
wasm32-wasip3  (Component Model + native async)
bash
# 编译到 WASI 0.3
cargo build --target wasm32-wasip3 --release

# 运行
wasmtime run --wasi 0.3 app.wasm

回调到 async 的机械迁移

rust
// 迁移前(WASI 0.2 回调风格)
fn read_file(path: &str, cb: Box<dyn Fn(Result<Vec<u8>, Error>)>) {
    let stream = wasi_fs::open(path, Read);
    stream.read_to_end(move |result| {
        cb(result.map_err(Error::from));
    });
}

// 迁移后(WASI 0.3 async 风格)
async fn read_file(path: &str) -> Result<Vec<u8>, Error> {
    let stream = wasi_fs::open(path, Read).await?;
    let data = stream.read_to_end().await?;
    Ok(data)
}

性能对比

指标WASI 0.2 (callback)WASI 0.3 (async)
冷启动~2 ms~2 ms
HTTP 请求延迟~0.5 ms~0.3 ms
并发 1000 请求串行回调,~500 ms并发 await,~15 ms
内存/请求~50 KB (callback context)~20 KB (task context)
最大并发~100 (回调栈限制)~10000 (轻量 task)

核心差异在于并发处理:WASI 0.2 的回调模型下每个回调占用完整的调用栈上下文;WASI 0.3 的 task 更轻量,可以高效调度上万个并发任务。

生产部署方案

wasmCloud + WASI 0.3

yaml
# wasmcloud.yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: async-proxy
spec:
  components:
    - name: http-proxy
      type: component
      properties:
        image: registry.acme.io/proxy:0.3.0
        config:
          - name: upstream_url
            value: "http://backend:8080"
          - name: timeout
            value: "5000"
      traits:
        - type: spread
          properties:
            replicas: 4
        - type: link
          properties:
            target: redis-cache
            source: http-proxy

边缘部署:Spin + WASI 0.3

toml
# spin.toml
[[component]]
id = "async-api"
source = "api.wasm"
[component.trigger]
type = "http"
base = "/api"
[component.config]
upstream = "https://api.upstream.com"
max_concurrent = "10000"
bash
# 部署到边缘
spin deploy --runtime wasi-0.3

容器化部署

dockerfile
# Dockerfile
FROM wasmtime/wasmtime:37-runtime
COPY app.wasm /app/app.wasm
EXPOSE 8080
CMD ["wasmtime", "serve", "--wasi", "0.3", "--addr", "0.0.0.0:8080", "/app/app.wasm"]

安全模型

async task 沙箱

WASI 0.3 的 async task 继承了 Component Model 的能力安全模型:

┌──────────────────────────────────────────────┐
│             Component Instance               │
│  ┌──────────────────────────────────────────┐│
│  │          async task                      ││
│  │  能力:wasi:http/outgoing-handler        ││
│  │  能力:wasi:filesystem/read /tmp/*       ││
│  │  能力:wasi:clocks/monotonic-clock       ││
│  │                                          ││
│  │  ✅ 可以发 HTTP 请求                     ││
│  │  ✅ 可以读 /tmp 下文件                   ││
│  │  ✅ 可以获取时间                         ││
│  │  ❌ 不能写文件                           ││
│  │  ❌ 不能开 TCP socket                    ││
│  │  ❌ 不能执行子进程                       ││
│  └──────────────────────────────────────────┘│
└──────────────────────────────────────────────┘

取消传播

rust
// WASI 0.3 的取消是一等公民——可以沿调用链传播
async fn handler(req: Request) -> Response {
    let ctx = CancellationToken::new();

    // 设置超时
    let timeout = tokio::time::timeout(
        Duration::from_secs(5),
        process_request(req, ctx.clone())
    );

    match timeout.await {
        Ok(result) => result,
        Err(_) => {
            ctx.cancel();  // 取消所有下游异步操作
            Response::timeout()
        }
    }
}

async fn process_request(req: Request, ctx: CancellationToken) -> Response {
    // 如果 ctx 被取消,下游的 db.query 和 http.post 都会自动中止
    let data = tokio::select! {
        r = db.query("...") => r.unwrap(),
        _ = ctx.cancelled() => return Response::cancelled(),
    };

    let resp = http.post("...", data).await;
    resp
}

总结

WASI 0.3 是 WebAssembly 服务端化的最后一块拼图。回顾三个版本:

  • WASI 0.1(2019)——"能用但不优雅"的 C 风格 ABI,无网络能力
  • WASI 0.2(2024)——Component Model 带来富类型和跨语言组合,但异步靠回调
  • WASI 0.3(2026)——原生 async/await,Wasm 服务端编程模型对齐主流语言

WASI 0.3 的意义不是"加了 async 关键字"——它是 Component Model 的成熟标志。async task 是一等公民,可以被调度、取消、传播;运行时提供事件循环,开发者不需要手写 callback 链;跨语言组件间的异步调用通过 WIT 接口声明,编译器自动生成 binding。

对于 Rust 开发者,这意味着 wasm32-wasip3 目标的代码与 x86_64-unknown-linux-gnu 目标的代码可以使用相同的 async/await 语法。对于 Go 和 Python 开发者,即将到来的 SDK 更新会带来对等的异步体验。

Wasm 不再只是"浏览器里的沙箱"——它是云原生的第四种运行时(容器 → VM → Serverless → Wasm),WASI 0.3 让它在异步场景下终于可以用了。

参考资料

上次更新于: