从前端调用后端
命令让前端请求原生后端执行操作。本章为最小项目添加一个可运行的问候表单,先用普通 JavaScript 展示原生通信边界;Todo 教程则使用共享 MoonBit 类型和 Rabbita。
添加契约依赖
在 moon.mod 已有的 import { ... } 块中加入下面一项,保留其他依赖:
"moonbit-community/proton_contract@0.4.0",
将 app/moon.pkg 替换为:
import {
"moonbitlang/core/json",
"moonbitlang/async",
"moonbit-community/proton",
"moonbit-community/proton_contract",
}
supported_targets = "native"
pkgtype(kind: "executable")
在项目根目录运行 moon update。模块依赖让库可用,包导入则让入口能使用 @proton_contract。
定义、注册并调用命令
将 app/main.mbt 替换为下面的完整示例:
///|
struct GreetRequest {
name : String
} derive(FromJson, ToJson)
///|
let greet : @proton_contract.Command[GreetRequest, String] =
@proton_contract.command("greet")
///|
async fn main {
let html =
#|<!doctype html>
#|<html lang="en">
#|<meta charset="utf-8">
#|<title>Greeting</title>
#|<style>body { font: 18px system-ui; padding: 32px; }</style>
#|<label>Name <input id="name" value="MoonBit"></label>
#|<button id="greet">Greet</button>
#|<p id="result" role="status"></p>
#|<script>
#| document.querySelector("#greet").onclick = async () => {
#| const result = document.querySelector("#result");
#| try {
#| result.textContent = await window.__MoonBit__.app.greet({
#| name: document.querySelector("#name").value
#| });
#| } catch (error) {
#| result.textContent = String(error);
#| }
#| };
#|</script>
#|</html>
@proton.html("Greeting", html)
..config(@proton.load_config())
..commands(fn(registrar) raise {
registrar.bind(greet, (_context, request) => {
"Hello, " + request.name + "!"
})
})
.run_or_abort()
}
执行 proton_cli dev。输入“Ada”,点击 Greet,页面应显示“Hello, Ada!”。
这里有三个相互连接的部分:
GreetRequest描述 JSON 请求,greet声明路由及响应类型。..commands(...)通过registrar.bind注册处理器,处理器接收调用方上下文和解码后的请求。window.__MoonBit__.app.greet(...)返回一个表示响应的 JavaScript promise,页面等待结果并处理拒绝。
仅声明描述符并不会注册处理器。应用中的命令名称需要保持唯一。
如果在普通浏览器打开页面,window.__MoonBit__ 不存在。请使用 proton_cli dev 启动桌面应用,而不是仅打开前端 URL。
参数与返回值
对象属性 name 对应请求字段。操作需要更多输入时,在请求中添加可序列化字段。返回值可以是字符串、数字、数组,也可以是实现 ToJson 的结构体。
原生处理器运行在后端,适合执行业务校验和原生操作。渲染器不能借此直接调用任意 MoonBit 函数。
错误处理
请求格式错误、命令不存在或处理器失败,都会使 JavaScript promise 被拒绝。保留调用外层的 try/catch,并显示有用的错误信息。
预期的业务结果应作为响应数据。例如 Todo 模板返回 Changed、InvalidTitle 或 MissingTodo,前端将它们与描述 bridge、传输和解码失败的 ClientFailure 分开处理。
不要因为前端按钮回调已经执行,就假定请求成功。依赖操作成功的界面更新,应等待返回结果。
异步操作与调用方上下文
命令处理器可以执行异步操作。上下文用于识别调用方,也可以向其发送类型化事件。向前端发送事件会在这个示例上继续修改。