You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust/WebAssembly流式HTTP请求:将ReadableStream的JsValue转为向量

问题:Rust/Wasm流式读取二进制文件并转换为16位整数向量

背景

作为Rust新手,我正在开发一个展示大量边的图形网页,计划用WebAssembly做图布局计算节点位置,并用WebGL绘图。需要流式请求一个约7MB的二进制文件,文件由16位整数组成,以最大值作为分隔符,每个索引对应目标节点索引。希望流式读取数据,拿到第一块后就开始布局。

问题

需要将流式响应的JsValue块正确转换为数组,最终目标是转为16位整数向量。

尝试的方案

初始可运行代码(lib.rs)

use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{ReadableStreamDefaultReader, Response};

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub async fn fetch_and_compute_graph() -> Result<JsValue, JsValue> {
    let window = web_sys::window().unwrap();
    let resp_promise = window.fetch_with_str(&"./edges.bin");
    let resp_value = JsFuture::from(resp_promise).await?;
    let resp: Response = resp_value.dyn_into().unwrap();
    log(&format!("Response status code: {}", resp.status()));
    if resp.status() != 200 {
        return Err(JsValue::FALSE);
    }
    let reader_obj = resp.body().unwrap().get_reader();
    let stream_reader: ReadableStreamDefaultReader = reader_obj.dyn_into().unwrap();

    // 后续尝试的代码添加在这里
}

尝试1:直接类型转换

尝试将read()返回的结果转为Uint8Array,但报错:

let chunk_obj = JsFuture::from(stream_reader.read()).await?;
let chunk_bytes: Uint8Array = chunk_obj.dyn_into().unwrap();

报错信息:

Uncaught (in promise) RuntimeError: unreachable executed
    __wbg_adapter_14 http://localhost:8080/pkg/rust_wasm_centrality.js:204
    real http://localhost:8080/pkg/rust_wasm_centrality.js:189
    promise callback*getImports/imports.wbg.__wbg_then_11f7a54d67b4bfad http://localhost:8080/pkg/rust_wasm_centrality.js:326
    __wbg_adapter_14 http://localhost:8080/pkg/rust_wasm_centrality.js:204
    real http://localhost:8080/pkg/rust_wasm_centrality.js:189
rust_wasm_centrality_bg.wasm:24649:1

尝试2:serde反序列化

用serde-wasm-bindgen反序列化,但结果转为了JS对象,不符合预期:

let chunk_obj = JsFuture::from(stream_reader.read()).await?;
let bytes = serde_wasm_bindgen::from_value(chunk_obj)?;

控制台输出错误:

Loading graph result: Error: invalid type: JsValue(Object({"done":false,"value":{"0":170,"1":4,"2":180,"3":4,...

解决方案

核心问题是ReadableStreamDefaultReader.read()返回的是包含done和value的JS对象,而非直接的Uint8Array。需要先解构这个对象,提取出二进制块,再转换为16位整数向量。

完整修正代码

use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{ReadableStreamDefaultReader, Response};
use std::io::{Cursor, Read};
use byteorder::{ByteOrder, LittleEndian}; // 需要添加byteorder依赖

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub async fn fetch_and_compute_graph() -> Result<JsValue, JsValue> {
    let window = web_sys::window().unwrap();
    let resp_promise = window.fetch_with_str(&"./edges.bin");
    let resp_value = JsFuture::from(resp_promise).await?;
    let resp: Response = resp_value.dyn_into().unwrap();
    log(&format!("Response status code: {}", resp.status()));
    if resp.status() != 200 {
        return Err(JsValue::FALSE);
    }
    let reader_obj = resp.body().unwrap().get_reader();
    let stream_reader: ReadableStreamDefaultReader = reader_obj.dyn_into().unwrap();

    loop {
        let result = JsFuture::from(stream_reader.read()).await?;
        // 解构done标记,判断流是否结束
        let done = js_sys::Reflect::get(&result, &JsValue::from_str("done"))?
            .as_bool()
            .ok_or("Failed to get 'done' flag")?;
        
        if done {
            log("Stream completed");
            break;
        }

        // 提取二进制块并转为Uint8Array
        let value = js_sys::Reflect::get(&result, &JsValue::from_str("value"))?;
        let chunk_bytes: Uint8Array = value.dyn_into()?;
        
        // 将Uint8Array复制到Rust的u8向量中
        let mut buffer = vec![0u8; chunk_bytes.length() as usize];
        chunk_bytes.copy_to(&mut buffer);

        // 转换为16位整数向量(根据实际字节序调整为BigEndian)
        let mut u16_vec = Vec::with_capacity(buffer.len() / 2);
        let mut cursor = Cursor::new(buffer);
        while let Ok(u16_val) = LittleEndian::read_u16(&mut cursor) {
            u16_vec.push(u16_val);
        }

        // 处理当前块的16位整数,比如启动布局计算
        log(&format!("Processed {} 16-bit integers", u16_vec.len()));
    }

    Ok(JsValue::UNDEFINED)
}

依赖调整

在Cargo.toml中添加byteorder依赖:

[dependencies]
# 原有依赖保留
byteorder = "1.4"

关键说明

  1. 解构流读取结果:通过js_sys::Reflect::get读取done和value属性,判断流是否结束并提取二进制块。
  2. JS数组转Rust向量:用copy_to方法将JS的Uint8Array复制到Rust的Vec<u8>中。
  3. 字节序处理:使用byteorder库解析16位整数,若二进制文件是大端字节序,替换LittleEndian为BigEndian即可。

内容的提问来源于stack exchange,提问作者Simon Crowe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 18:10:26