JSBridge 不是发消息就完了:runJavaScript、Timeout 和 Callback Lost
项目地址:github.com/lichenyang5...
本文是 MiniAppRuntime-Harmony 系列的第五篇,重点讲回调执行、超时和 callback lost。
1. 能调用成功,不代表 JSBridge 完成了
很多 JSBridge Demo 到这里就结束了:
text
H5 调 ArkTS
ArkTS 回调 H5
但是实际框架不能只考虑成功路径。
还要考虑:
- ArkTS 没有回调怎么办?
- H5 页面已经超时了,ArkTS 结果才回来怎么办?
- requestId 找不到 callback 怎么办?
- JSON 字符串里有引号,runJavaScript 拼坏怎么办?
- BridgeController 里直接拼 JS 字符串,会不会越来越乱?
所以这个阶段做了两件事:
text
抽出 BridgeCallbackExecutor
补充 TIMEOUT / CALLBACK_LOST
2. 为什么要抽出 BridgeCallbackExecutor
一开始,BridgeController 可以直接调用:
ts
this.controller.runJavaScript(...)
但是这样会让 Controller 变重。
它既要接收消息,又要分发请求,还要拼 JS 字符串,还要处理回调异常。
所以我把回调逻辑抽出来:
text
BridgeCallbackExecutor
它只做一件事:
把 BridgeResponse 安全送回 H5。
职责包括:
- 序列化响应;
- 安全转义 JSON 字符串;
- 拼接 H5 回调函数;
- 调用
runJavaScript; - 记录成功或失败日志。
BridgeController 只需要:
ts
await this.callbackExecutor.sendResponse(response)
这样职责就清楚了。
3. 为什么 JSON 要安全转义
ArkTS 回调 H5 时,本质上是执行一段 JS:
js
window.__myascf_on_native_response__('{...}')
如果 response 里面有引号:
json
{
"message": "hello 'world'"
}
就可能导致脚本字符串拼接出错。
所以不能简单拼字符串。
更稳妥的方式是,把 response JSON 再作为 JS 字符串参数处理,确保引号、换行等字符不会破坏脚本。
这类细节放在 CallbackExecutor 里,比散落在各个业务 handler 里更安全。
4. H5 为什么需要 timeout
H5 调用 ArkTS,本质是异步请求。
异步请求一定要有超时。
否则如果 ArkTS 因为某种原因没有回调,H5 Promise 会一直 pending。
所以 window.myascf.send 支持 timeout:
js
window.myascf.send("ui.showToast", {
message: "hello"
}, {
timeout: 5000
})
H5 侧 callback map 保存的不只是 resolve / reject,还包括 timer:
js
callbacks.set(requestId, {
resolve: resolve,
reject: reject,
timer: timer,
action: action,
createdAt: Date.now()
})
如果超时前收到响应:
text
clearTimeout(timer)
delete callback
resolve / reject
如果超时后还没收到响应:
text
delete callback
reject TIMEOUT
5. 什么是 CALLBACK_LOST
一种常见情况是:
text
H5 请求超时
→ callback 被删除
→ ArkTS 后来才回调
→ H5 找不到 requestId
这就是 callback lost。
这不是致命错误。
正确做法是:
js
if (!callback) {
console.warn('CALLBACK_LOST', response.requestId)
return
}
不要让页面崩掉。
不要影响其他请求。
只记录日志。
6. H5 回调生命周期
完整生命周期如下:
text
send 开始
→ 创建 requestId
→ 创建 Promise
→ 创建 timeout timer
→ callbacks.set(requestId, callback)
→ 发送请求给 ArkTS
→ 收到响应
→ clearTimeout
→ callbacks.delete(requestId)
→ code === 0 ? resolve : reject
如果超时:
text
send 开始
→ callbacks.set
→ timeout 到期
→ callbacks.delete
→ reject TIMEOUT
→ 后续响应回来时 CALLBACK_LOST
7. Demo 里怎么验证
页面可以提供几个按钮:
text
正常调用 ui.showToast
参数错误调用 ui.showToast
未知 action 调用 debug.unknownAction
极短 timeout 测试
例如:
js
window.myascf.send("ui.showToast", {
message: "timeout test"
}, {
timeout: 1
})
如果链路足够快,timeout 可能不稳定触发。
这时可以后续加一个 mock delay handler 来专门测试。
8. 这一阶段的意义
到这里,JSBridge 已经不仅是"能发消息"。
它开始具备框架应有的稳定性:
- 成功响应;
- 参数错误;
- 未知 action;
- 内部异常;
- 超时;
- callback lost;
- 安全回调;
- 职责分层。
后续可以继续扩展两个方向:
第一,新增更多 API,比如 clipboard。
第二,做一个调试面板,把每次调用的 requestId、action、耗时、结果展示出来。
项目地址:
text
https://github.com/lichenyang5/MiniAppRuntime-Harmony