euv 中的异步操作
现代 Web 应用本质上是异步的。无论你是从 API 获取数据、从用户设备读取文件,还是等待定时器过期,代码都需要处理在未来某个时刻完成的操作。euv 提供了一套强大的异步原语——包括 spawn_local、JsFuture、JSON 处理、Promise 和浏览器 Response 对象——使得在 Web 中编写异步 Rust 代码成为可能。本文将探讨 euv 中所有可用的异步功能。
理解浏览器上下文中的异步
在传统 Rust 应用中,你可能会使用 tokio 或 async-std 进行异步操作。然而,euv 应用通过 WebAssembly(WASM)在浏览器中运行,浏览器具有单线程事件循环。euv 不使用多线程运行时,而是利用浏览器原生的异步原语——Promise、fetch 和回调——并将它们包装成对 Rust 友好的 API。
euv 中的关键异步原语包括:
spawn_local:在浏览器事件循环上生成异步任务。JsFuture:将 JavaScript Promise 包装为 RustFuture。- JSON 工具:解析和序列化来自 JavaScript 的 JSON 数据。
- Promise / Response:直接访问浏览器异步 API。
spawn_local —— 生成异步任务
spawn_local 是在 euv 中启动异步任务的主要方式。它在浏览器事件循环上调度一个 future 来运行,类似于 tokio::spawn 在服务端 Rust 中的工作方式。
spawn_local(async {
// 这段代码异步运行
let result = some_async_operation().await;
// 处理结果
});
spawn_local 接收一个 async 块(或任何实现 Future 的类型)并调度它执行。future 将由浏览器事件循环轮询,当它完成时,任何 .await 点都会恢复执行。
spawn_local 的常见用例包括:
- 数据获取:向 API 发起 HTTP 请求而不阻塞 UI。
- 定时器:调度延迟或周期性操作。
- 文件读取:异步读取用户选择的文件。
- 复杂计算:卸载工作以避免阻塞主线程。
JsFuture —— 将 JavaScript Promise 桥接到 Rust
JsFuture 是一个包装器,将 JavaScript 的 Promise 转换为 Rust 的 Future。这是 JavaScript 异步世界和 Rust async/await 语法之间的桥梁。
当你调用返回 Promise 的浏览器 API(如 fetch)时,可以将结果包装在 JsFuture 中,然后在 Rust 中 .await 它:
let future = JsFuture::from(some_js_promise());
let result = future.await;
JsFuture 实现了标准的 Rust Future trait,因此它可以与 Rust 的 async/await 语法无缝配合,也可以与其他 future 组合使用。
JSON 处理
euv 提供了处理 JSON 数据的工具,JSON 是 Web API 的通用语言。你可以:
- 解析 JSON:将来自 JavaScript 的 JSON 字符串转换为 Rust 数据结构。
- 序列化为 JSON:将 Rust 数据结构转换为 JSON 字符串以发送给 API。
JSON 处理对于任何与后端服务器通信的应用都是必不可少的。结合 fetch(通过 JsFuture),你可以构建完整的数据获取管线:
- 使用浏览器的
fetchAPI 发起 HTTP 请求。 - 将生成的 Promise 包装在
JsFuture中。 - 等待响应并提取 JSON 数据。
- 将 JSON 解析为 Rust 类型。
Promise 和 Response 对象
euv 提供了对浏览器 Promise 和 Response 对象的直接访问,使你能够使用浏览器异步 API 的全部功能。
Promise
Promise 表示一个未来才会可用的值。在 euv 中,你可以创建 Promise、解析它们,并用 .then() 和 .catch() 链接它们。
Response
Response 表示 HTTP 请求的结果。它提供了以各种格式访问响应体的方法:
.json():将响应体解析为 JSON。.text():将响应体作为纯文本字符串获取。.array_buffer():将响应体作为ArrayBuffer获取,用于二进制数据。
与 JsFuture 结合使用时,你可以等待 Response 然后提取数据:
let response = JsFuture::from(fetch(url)).await;
let json_data = JsFuture::from(response.json()).await;
从 API 获取数据
Web 应用中最常见的异步操作之一是从 REST API 获取数据。在 euv 中,这涉及:
- 使用浏览器的
fetch函数发起 HTTP 请求。 - 将生成的 Promise 包装在
JsFuture中。 - 等待响应并提取数据。
以下是通用模式:
spawn_local(async {
let response = JsFuture::from(fetch("https://api.example.com/data")).await;
// 处理响应
let data = JsFuture::from(response.json()).await;
// 使用数据更新信号
});
fetch 函数可通过浏览器 Window 对象使用,euv 提供了对该对象的访问。
异步代码中的错误处理
异步操作可能因多种原因失败:网络错误、服务器错误、超时、数据格式错误等。正确的错误处理对于良好的用户体验至关重要。
在 euv 中,你使用 Rust 的标准错误处理机制来处理异步错误:
Result:大多数异步操作返回Result类型。使用match或?运算符处理成功和失败情况。unwrap_or_default():对于失败时可以使用默认值的操作。- 信号更新:更新错误状态信号以在 UI 中显示错误消息。
spawn_local(async {
let result = some_async_operation().await;
match result {
Ok(data) => {
// 更新成功状态
}
Err(error) => {
// 更新错误状态
}
}
});
组合多个异步操作
通常,你需要同时或按顺序执行多个异步操作。euv 支持这两种模式:
顺序执行
只需一个接一个地 .await 每个操作:
let first = operation_one().await;
let second = operation_two(first).await;
并发执行
使用 Rust 的 join! 宏或 FuturesUnordered 同时运行多个 future:
let (result1, result2) = futures::join!(operation_one(), operation_two());
异步与响应式信号
euv 异步系统的真正威力在于将其与响应式信号结合使用。你可以:
- 在开始异步操作之前设置加载状态信号。
- 使用
spawn_local生成异步任务。 - 在操作完成时更新数据或错误信号。
let shared_text: Signal<String> = use_signal(|| "Type here...".to_string());
spawn_local(async {
// 执行异步工作
let result = fetch_data().await;
// 用结果更新信号
shared_text.set(result);
});
这种模式在异步操作进行时保持 UI 响应,并在数据到达时自动更新 UI。
实践示例:带异步数据的温度转换器
考虑一个从天气 API 获取当前温度的温度转换器:
watch!(celsius, |celsius_value: f64| {
fahrenheit.set(celsius_value * 9.0 / 5.0 + 32.0);
});
你可以扩展此模式以从 API 获取初始温度:
spawn_local(async {
let response = JsFuture::from(fetch("https://api.weather.com/temperature")).await;
let data = JsFuture::from(response.json()).await;
// 解析并设置初始摄氏度值
});
watch! 宏确保每当摄氏度值变化时(无论是来自 API 还是用户输入),华氏度值都会自动更新。
总结
euv 提供了一套全面的异步原语,用于构建现代 Web 应用:
spawn_local:在浏览器事件循环上生成异步任务。JsFuture:将 JavaScript Promise 包装为 Rust Future。- JSON 工具:解析和序列化 JSON 数据。
- Promise / Response:直接访问浏览器异步 API。
- 错误处理:使用
Result、match和?的标准 Rust 错误处理。 - 信号集成:从异步回调中更新响应式信号。
通过掌握这些异步原语,你可以构建能够与 API 通信、处理用户文件并执行复杂异步操作的 euv 应用——同时保持 UI 响应性,代码干净且可维护。