JavaScriptProxy 和 runJavaScript:ASCF 里两根最重要的桥

JavaScriptProxy 和 runJavaScript:ASCF 里两根最重要的桥

看 ASCF 这类架构,最先要搞清楚的不是 IPC,也不是 JSVM,而是这两个东西:

text 复制代码
JavaScriptProxy
runJavaScript

它们一个负责 H5 调 ArkTS,一个负责 ArkTS 回调 H5。

如果这两个方向没搞清楚,后面讲 Controller、Dispatcher、JSAPI、NAPI 都会乱。


一、先从一个按钮开始

H5 页面里有一个按钮:

html 复制代码
<button onclick="callNative()">调用 ArkTS</button>

点击之后,H5 希望把请求发给 ArkTS。

在 MyASCF 里,它会这样写:

js 复制代码
window.ascfBridge.send(JSON.stringify({
  requestId: 'req_001',
  action: 'runtime.ping',
  params: {
    from: 'h5'
  },
  timeout: 5000
}))

这里的 window.ascfBridge 不是浏览器原生对象。

它是 ArkTS 通过 ArkWeb 注入给 H5 的对象。

这就是 JavaScriptProxy


二、JavaScriptProxy:H5 调 ArkTS

JavaScriptProxy 解决的问题是:

前端页面里的 JavaScript,怎么调用应用侧 ArkTS 方法?

ArkTS 侧注册一个对象:

ts 复制代码
.javaScriptProxy({
  object: this.bridge,
  name: 'ascfBridge',
  methodList: ['send'],
  controller: this.webController
})

H5 侧就可以调用:

js 复制代码
window.ascfBridge.send(rawReq)

所以它的方向是:

text 复制代码
H5 / JavaScript
  ↓
JavaScriptProxy
  ↓
ArkTS

这句话很重要:

text 复制代码
JavaScriptProxy 是 H5 → ArkTS。

它不是 ArkTS 回调 H5。


三、AscfBridgeObject:桥对象只是入口

在 MyASCF 里,ascfBridge 对应 ArkTS 侧的 AscfBridgeObject

它大概做这件事:

text 复制代码
H5 调 window.ascfBridge.send(rawReq)
  ↓
AscfBridgeObject.send(rawReq)
  ↓
BridgeController.handle(rawReq)

注意,AscfBridgeObject 不应该承担太多业务。

它最好只做入口:

text 复制代码
打印日志
接收 rawReq
交给 Controller

因为真正的协议解析、分发、业务处理,应该交给后面的层:

text 复制代码
Controller
Dispatcher
Register
Biz
Imp

这样桥对象就不会越来越臃肿。


四、为什么 H5 不直接拿 send 的返回值?

一开始你可能会这样写:

js 复制代码
const res = window.ascfBridge.send(rawReq)

这看起来很简单。

但是 ASCF 这种能力调用,更接近异步模型:

text 复制代码
H5 发请求
ArkTS 处理
可能调用系统 API
可能调用 C++
可能失败
可能超时
最后再回调 H5

所以更好的做法是 Promise:

js 复制代码
window.ascf.send('runtime.ping', params)
  .then(res => {
    console.log('success', res)
  })
  .catch(err => {
    console.log('failed', err)
  })

H5 发出去之后,不是马上拿返回值,而是等 ArkTS 处理完,再通过回调回来。

这时候就需要第二根桥:runJavaScript


五、runJavaScript:ArkTS 回调 H5

runJavaScript 解决的问题是:

ArkTS 怎么主动调用 H5 页面里的 JavaScript 函数?

H5 先定义一个全局函数:

js 复制代码
window.__ascfResolve = function(response) {
  const item = window.ascf.pendingMap.get(response.requestId)

  if (!item) {
    console.log('pending request not found')
    return
  }

  window.ascf.pendingMap.delete(response.requestId)

  if (response.code === 0) {
    item.resolve(response)
  } else {
    item.reject(response)
  }
}

ArkTS 处理完之后:

ts 复制代码
this.webController.runJavaScript(
  `window.__ascfResolve(${responseJson})`
)

这时 H5 页面里的 window.__ascfResolve 被执行。

所以方向是:

text 复制代码
ArkTS
  ↓
runJavaScript
  ↓
H5 / JavaScript

记住:

text 复制代码
runJavaScript 是 ArkTS → H5。

六、pendingMap:Promise 怎么和回调对应起来?

H5 发请求时,会生成一个 requestId

js 复制代码
const requestId = 'req_' + Date.now()

然后把 Promise 的 resolvereject 保存起来:

js 复制代码
window.ascf.pendingMap.set(requestId, {
  resolve,
  reject
})

请求发给 ArkTS:

js 复制代码
window.ascfBridge.send(JSON.stringify(request))

ArkTS 处理完之后,回调 H5:

js 复制代码
window.__ascfResolve({
  requestId: 'req_001',
  code: 0,
  message: 'success',
  data: {}
})

H5 再用 requestId 找到对应 Promise:

js 复制代码
const item = window.ascf.pendingMap.get(response.requestId)

所以 requestId 是异步回调的钥匙。

没有它,多个请求同时发出时,H5 就不知道哪个结果对应哪个请求。


七、两根桥合起来就是 JSBridge

现在可以把主链路写清楚了:

text 复制代码
H5
  ↓
window.ascf.send(...)
  ↓
window.ascfBridge.send(rawReq)
  ↓
JavaScriptProxy
  ↓
ArkTS Controller / Dispatcher / Biz / Imp
  ↓
runJavaScript
  ↓
window.__ascfResolve(response)
  ↓
H5 Promise resolve

这就是 MyASCF 的 JSBridge 模型。

注意,不要把这两个方向说反:

text 复制代码
JavaScriptProxy:H5 → ArkTS
runJavaScript:ArkTS → H5

八、JavaScriptProxy 和 runJavaScript 不是 IPC

这两个 API 是你在应用层能看到的接口。

但是如果 H5 页面运行在 WebView 渲染进程,ArkTS 逻辑运行在应用主进程,那么底层跨进程传消息时,框架内部可能会走 IPC。

所以层级关系应该是:

text 复制代码
应用层能看到:
JavaScriptProxy / runJavaScript

框架底层可能涉及:
IPC / RenderProcess / Main Process

不能简单说:

text 复制代码
JavaScriptProxy = IPC
runJavaScript = IPC

更准确是:

text 复制代码
JavaScriptProxy 和 runJavaScript 是上层桥接口;
IPC 是底层跨进程通信机制。

九、在 MyASCF 里如何验证?

你可以在 H5 和 ArkTS 两边都打日志。

H5:

text 复制代码
[H5] create request
[H5] call window.ascfBridge.send
[H5] window.__ascfResolve called
[H5] Promise resolve

ArkTS:

text 复制代码
[ASCF][Bridge] JavaScriptProxy send rawReq
[ASCF][Controller] parsed request
[ASCF][Dispatcher] dispatch action
[ASCF][Register] get handler
[ASCF][Controller] callbackToH5
[ASCF][WebRuntimePage] runJavaScript

看到这组日志,就说明两根桥跑通了。


十、总结

这篇只记住一句话:

text 复制代码
JavaScriptProxy 解决 H5 调 ArkTS。
runJavaScript 解决 ArkTS 回调 H5。

它们合起来,让 H5 和 ArkTS 可以形成一次完整的异步调用。

但是,它们不是 IPC,也不是 JSVM,也不是 NAPI。

下一篇再讲这些运行时概念:

text 复制代码
IPC、JSVM、UIThread、libuv 到底分别是什么?

参考资料

相关推荐
浮生望4 分钟前
BFF架构实战:用Express+SSE为Vue3搭建安全的流式对话中间层
前端
先吃饱再说27 分钟前
React 组件通信:从 Props 单向传递到自定义事件
前端·react.js·前端框架
swipe32 分钟前
05|(前端转后全栈)不手写一堆 SQL,后端怎么操作数据库?MyBatis-Plus 入门
前端·后端·全栈
swipe37 分钟前
04|(前端转后全栈)前端状态为什么不够用?从页面数据到 MySQL 持久化
前端·后端·全栈
橘子星37 分钟前
别光看教程!手把手拆解 React Todo 项目,一文吃透 5 个核心概念
前端·javascript
大白要努力!43 分钟前
纯前端实现 PDF 加水印工具 —— 零后端、支持中文、实时预览
前端·pdf·html
石小石Orz1 小时前
TRAE SOLO实战:实现一个桌面3D助手
前端·人工智能
蜡台1 小时前
使用 uni-popup 实现数据选择器Data-Picker
前端·javascript·html·uniapp·uni-popup·data-picker
拆房老料1 小时前
BaseMetas FileView 1.2.0 发布:Office/WPS 大文件预览与内存安全优化实测
前端·产品运营·开源软件
blns_yxl1 小时前
Promise封装Fetch + 重试机制(HTML+JS)
前端·javascript·html