JSBridge 不是发消息就完了:runJavaScript、Timeout 和 Callback Lost

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
相关推荐
Csvn19 分钟前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
Csvn21 分钟前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端
濮水大叔1 小时前
不必把 Vue3 写成“麻花”:从状态碎片到对象协作,重新理解 Zova 的前端心智模型
前端·typescript·vue3·ioc·tsx·zova
Vuji2 小时前
MCP: 一条 tool 调用链路的旅程
前端·人工智能
万少3 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端
明月_清风3 小时前
🚀 Palantir Foundry 本体论实战:当 Ontology 从"知识图谱"进化为"企业操作系统"
前端·后端
驳是3 小时前
入坑 Nginx,看这一篇就够了
前端
宁风NF3 小时前
JavaScript:网络请求与前端通信
开发语言·前端·javascript·网络·学习·ecmascript
明月_清风3 小时前
从概念到代码:用 Ontology 构建你的第一个知识图谱
前端·后端
程序员黑豆3 小时前
鸿蒙应用开发:AppStorage 全局状态存储用法教程
前端·harmonyos