从 0 新增一个 has.echo:我如何理解小程序容器里的 API 调用链路
从 0 新增一个has.echo我如何理解小程序容器里的 API 调用链路说明本文是一次脱敏后的学习复盘。文中的has.echo、EchoModule、system.demo都是为了讲清楚调用链路而设计的最小示例不包含公司内部源码、真实业务接口、真实模块路径和敏感实现细节。最近在学习一个类似“小程序容器”的运行时框架。它的形态和很多小程序框架比较像业务页面不会直接调用系统能力而是通过一个全局对象调用框架暴露的 API。比如我们希望业务侧可以这样写has.echo({message:hello runtime,success(res){console.log(success:,res)},fail(err){console.log(fail:,err)},complete(){console.log(complete)}})这个echoAPI 的功能很简单业务侧传入一个message底层收到参数后调用 ArkUI 弹窗展示这段内容底层再把{ message }返回给业务侧业务侧在success回调里拿到结果。功能虽然简单但它刚好能帮我走通一条完整链路业务页面 has.echo ↓ 全局 has 对象 ↓ 逻辑层 echo.js ↓ requireAPI(system.demo) ↓ 核心模块 EchoModule ↓ ArkUI 弹窗能力 ↓ Promise.resolve 返回数据 ↓ success / complete 回到业务页面这篇文章就记录一下如果从 0 新增一个has.echo需要改哪些地方以及为什么只写一个函数是不够的。一、先从问题开始为什么只写echo.js不够刚开始我以为新增一个 API 很简单写一个 echo.js 把它挂到 has 上 页面调用 has.echo但真正调试后发现这个理解太简单了。在这种运行时框架里一个 API 能不能被业务页面正常调用至少要经过几层1. 底层能力是否存在 2. 底层模块是否被注册 3. 逻辑层是否能 require 到底层模块 4. 全局 has 是否暴露了这个 API 5. 当前 API 版本是否允许对外发布 6. 异步回调机制是否认识这个 API任何一层漏掉最后表现出来的问题都可能很迷惑。比如has.echo is not a function requireAPI 找不到模块 底层方法执行了但 success 不触发 页面拿到 undefined complete 没有回调所以这次我用has.echo做了一个最小闭环把这条链路完整走了一遍。二、第一步在核心模块实现EchoModule新增 API 的第一步不是先写页面也不是先写has.echo而是先实现真正干活的底层模块。这里我用一个示例模块EchoModule。它的职责很简单接收 JS 层传入的 params ↓ 读取 params.message ↓ 调用 ArkUI 弹窗展示 message ↓ Promise.resolve 返回结果示意代码如下exportclassEchoModule{JsMethod({callback:true})echo(params:Recordstring,Object):PromiseRecordstring,Object{constoptionsparamsasRecordstring,Objectconstmessageoptions[message]asstring// 这里调用 ArkUI 弹窗能力。// 注意这里是脱敏示例不贴真实工程实现。// showDialog(message)returnPromise.resolve({message})}}这里有两个关键点。第一个是JsMethod。它表示这个方法可以被 JS 层调用。如果底层方法没有通过框架约定的方式暴露出来逻辑层就算知道模块名也没有办法正常调用。第二个是callback: true。它表示这个方法支持异步回调模式。底层返回的 Promise 结果最终会被框架包装成业务侧的success / fail / complete回调。也就是说底层这里返回returnPromise.resolve({message})业务侧最后可以在success里拿到success(res){console.log(res.message)}三、第二步在核心模块初始化时注册底层能力只写EchoModule还不够。因为框架运行时并不会自动知道这个模块存在。我们还需要在核心模块初始化阶段把这个模块注册进去。示意代码如下addApiModule({system.demo:()newEchoModule()})这一步的意义是告诉运行时现在底层有一个模块叫 system.demo。 当 JS 层调用 requireAPI(system.demo) 时 可以返回一个 EchoModule 实例。注册完成后底层能力可以抽象理解为system.demo.echo到这里只是说明“底层有能力了”。但是业务页面还不能直接写has.echo因为中间还缺少逻辑层转发和 API 暴露。四、第三步在逻辑层新增echo.js底层模块注册完成后需要在逻辑层新增一个对应的 API 文件。它的作用是把业务侧的has.echo转发到底层的system.demo.echo。示意代码如下import{requireAPI}from./require-apiexportfunctionecho(params{}){constdemorequireAPI(system.demo)returndemo.echo(params)}这层看起来很薄但它非常关键。因为业务页面调用的是has.echo(...)它不是直接调用system.demo.echo(...)所以echo.js的职责就是做一次桥接has.echo(params) ↓ ui/echo.js ↓ requireAPI(system.demo) ↓ demo.echo(params)这里还有一个好处逻辑层可以统一做参数校验、默认值处理、错误包装、兼容处理而不是把这些逻辑散落到底层模块里。五、第四步在has初始化入口导入echo创建了echo.js之后还需要在has初始化入口把它导入进去。否则文件虽然存在但全局has对象上并不会有这个方法。示意代码如下import{echo}from./ui/echoconsthas{echo,// other api...}完成这一步后应用初始化时才有机会生成window.has.echo如果忘了这一步页面调用时大概率会遇到has.echo is not a function这类错误说明底层能力可能已经写好了但 API 没有真正暴露给业务侧。六、第五步把echo加入接口白名单很多运行时框架不会让所有函数都随便暴露出去而是会维护一个 API 白名单。所以除了导入echo还需要把它加入接口列表。可以抽象理解为consthasInterfaceList[getSystemInfo,showToast,echo]这一步的作用是告诉框架echo 是一个合法 API。 初始化全局 has 时可以把它挂出去。如果这一步漏了可能出现一种情况代码文件存在 底层模块也存在 但是框架认为 echo 不是合法 API 所以业务侧无法正常调用这里我理解到一个点EchoModule 解决的是“底层有没有能力” hasInterfaceList 解决的是“这个能力允不允许暴露给业务”。两者不是一回事。七、第六步加入当前版本的公开 API 列表除了接口白名单框架里通常还会有“版本维度”的 API 发布表。例如某个版本支持哪些 API另一个版本支持哪些 API。新增echo时也需要把它加入当前版本对应的公开 API 列表。示意结构如下constpublishAPI{v1006:[getSystemInfo,showToast,echo]}这一步的意义是不是所有实现过的 API 都会对外发布。 只有加入当前版本的公开 API 列表业务侧才应该认为它可用。这个设计其实很常见。比如框架要做版本兼容、灰度能力、平台差异能力就不能只看“代码里有没有实现”还要看“当前版本允不允许用”。所以新增 API 时要同时关注两个问题这个 API 有没有实现 这个 API 有没有被当前版本发布八、第七步在异步 API 注册表中注册system.demo.echo这是我这次排查里最容易漏、也最关键的一步。has.echo是一个异步 API业务侧使用的是has.echo({success(res){},fail(err){},complete(){}})这种回调形式通常需要框架的异步 API 管理逻辑参与。所以还要把它加入异步 API 注册表。示意代码如下constasyncApis{system.demo:[echo]}这一步的作用是告诉框架system.demo.echo 是一个异步 API。 调用它时需要走 success / fail / complete 的回调包装逻辑。如果忘了这一步可能会出现一个非常迷惑的问题页面传入的 success 确实是 function 底层方法也确实执行了 但是 success 就是不触发。这个问题从页面看很像“回调丢了”。但本质不是业务侧没有传回调而是框架的异步 API 注册表没有认识这个新 API所以没有正确走回调包装流程。补上注册后success才能正常拿到Promise.resolve(...)返回的数据。九、业务页面最终怎么调用前面的步骤全部完成后应用初始化时会自动生成全局has对象。业务页面就可以直接调用has.echo({message:hello runtime,success(res){console.log(echo success:,res)},fail(err){console.log(echo fail:,err)},complete(){console.log(echo complete)}})完整执行流程如下业务页面调用 has.echo ↓ 进入逻辑层 echo.js ↓ echo.js 通过 requireAPI(system.demo) 获取底层模块 ↓ 调用 EchoModule.echo ↓ EchoModule 读取 params.message ↓ 调用 ArkUI 弹窗展示 message ↓ Promise.resolve({ message }) ↓ 框架把 Promise 结果包装成 success 回调 ↓ 业务侧 success(res) 拿到返回数据 ↓ complete 执行最终业务侧可以拿到类似结果{message:hello runtime}十、完整顺序总结以has.echo为例从 0 新增一个 API完整顺序可以整理成这样1. 在核心模块新增 EchoModule 2. 在 EchoModule 中使用 JsMethod({ callback: true }) 暴露 echo 方法 3. echo 方法接收 params读取 message 4. echo 方法调用 ArkUI 弹窗能力 5. echo 方法通过 Promise.resolve({ message }) 返回数据 6. 在核心模块初始化逻辑中注册 system.demo 7. 在逻辑层新增 echo.js 8. echo.js 中通过 requireAPI(system.demo) 调用底层能力 9. 在 has 初始化入口中导入 echo 10. 在接口白名单中加入 echo 11. 在当前版本的公开 API 列表中加入 echo 12. 在异步 API 注册表中加入 system.demo: [echo] 13. 应用初始化后生成全局 has.echo 14. 业务页面调用 has.echo并在 success 中拿到返回结果把它画成链路图就是核心模块 EchoModule ↓ 注册到底层模块表 system.demo ↓ 被逻辑层 requireAPI 获取 ui/echo.js ↓ 被 has 初始化导入 has.echo ↓ 被接口白名单和版本发布表允许 业务页面可调用 ↓ 被异步 API 表识别 success / fail / complete 正常回调十一、每一层漏掉会发生什么新增 API 时最好不要只看“我写了没有”而要看“每一层有没有连上”。可以用下面这张表自查漏掉的位置可能现象底层模块没写逻辑层调用到底层时报错模块没有注册requireAPI(system.demo)找不到模块逻辑层echo.js没写has.echo没有转发入口has初始化没导入has.echo is not a function接口白名单没加框架不认为它是合法 API版本发布表没加当前版本不对外暴露该 API异步 API 表没加success / complete可能不触发Promise 没返回数据success(res)里可能拿到空值这也是这次调试最大的收获新增 API 不是改一个函数而是打通一条链路。十二、这条链路背后的框架设计从has.echo这个小 API 反过来看框架其实做了几层隔离。第一层是业务层。业务开发者只关心has.echo({success,fail,complete})他们不需要知道底层是 ArkUI、ArkTS还是其他系统能力。第二层是逻辑层。逻辑层负责把has.echo转成底层模块调用例如has.echo → requireAPI(system.demo) → demo.echo这一层适合做参数处理、兼容处理、错误包装。第三层是核心能力层。核心层真正调用系统能力比如弹窗、设备信息、网络、定位等。第四层是框架治理层。包括接口白名单 版本发布表 异步 API 注册表 回调包装机制 模块注册表这些东西看起来繁琐但它们解决的是一个框架必须面对的问题哪些能力能用 哪个版本能用 调用方式是同步还是异步 底层模块从哪里找 结果如何返回给业务侧理解这几层之后再看运行时源码就不会觉得它是一堆零散文件而是能看出它的分工。十三、最终结论has.echo的功能很小但它完整覆盖了新增 API 的核心流程。它让我理解到一个小程序容器里的 API并不是页面函数到系统能力的直接调用。 它中间要经过 API 暴露、版本控制、模块注册、逻辑转发、异步回调包装等多层处理。所以以后新增类似 API 时可以按照同一套模板底层能力实现 ↓ 底层模块注册 ↓ 逻辑层 API 文件 ↓ has 初始化导入 ↓ 接口白名单 ↓ 版本发布表 ↓ 异步 API 注册表 ↓ 业务页面调用验证这条链路跑通之后再去看更复杂的 API就不再只是看某一个文件而是能顺着调用链路一步步定位问题。这也是我这次新增has.echo最大的收获。

相关新闻

151、RAG 检索增强生成(五):评估体系——RAGAS 指标、人工评测与线上监控

151、RAG 检索增强生成(五):评估体系——RAGAS 指标、人工评测与线上监控

151、RAG 检索增强生成(五):评估体系——RAGAS 指标、人工评测与线上监控 上周五凌晨两点,我被生产环境告警电话吵醒。用户反馈一个法律咨询机器人回答“根据《民法典》第X条,您需要承担全部责任”,但实际法条引用完全错误。我查了日志,发现RAG检索到的文档片段是某篇自…

2026/7/6 21:26:25 阅读更多 →
MySQL 联表查询性能对比:INNER JOIN vs 子查询 vs 临时表,3方案效率实测

MySQL 联表查询性能对比:INNER JOIN vs 子查询 vs 临时表,3方案效率实测

MySQL 联表查询性能优化实战:INNER JOIN vs 子查询 vs 临时表在数据库应用开发中,联表查询是最常见也最容易出现性能问题的操作之一。面对复杂的业务场景,开发者往往需要在多种实现方案中做出选择。本文将针对学生-课程-教师这一经典场景&…

2026/7/6 21:26:25 阅读更多 →
SDF在游戏开发中的创新应用:Dreams与Claybook技术案例分析

SDF在游戏开发中的创新应用:Dreams与Claybook技术案例分析

SDF在游戏开发中的创新应用:Dreams与Claybook技术案例分析 【免费下载链接】SDF Collection of resources (papers, links, discussions, shadertoys,...) related to Signed Distance Field 项目地址: https://gitcode.com/gh_mirrors/sdf/SDF 有符号距离场…

2026/7/6 21:22:23 阅读更多 →

最新新闻

直流电机驱动系统优化:TC78H660FTG与PIC18F87J11方案

直流电机驱动系统优化:TC78H660FTG与PIC18F87J11方案

1. 项目背景与核心器件选型 在工业自动化和消费电子领域,直流电机驱动系统的效率优化一直是工程师面临的关键挑战。TC78H660FTG作为东芝新一代H桥驱动器,与Microchip的PIC18F87J11微控制器组合,为解决这一问题提供了创新方案。 1.1 TC78H66…

2026/7/6 22:19:03 阅读更多 →
Flink Web UI未授权访问漏洞修复实战:从原理到Nginx反向代理加固

Flink Web UI未授权访问漏洞修复实战:从原理到Nginx反向代理加固

1. 项目概述:一次真实的Flink安全加固实战最近在梳理线上数据平台的资产时,安全扫描工具突然弹出一个高危告警:Apache Flink Web Dashboard存在未授权访问漏洞。这个告警让我心头一紧,因为这意味着任何能访问到Flink Web UI地址的…

2026/7/6 22:15:01 阅读更多 →
基于Playwright与飞书API构建电商数据自动化采集与同步系统

基于Playwright与飞书API构建电商数据自动化采集与同步系统

1. 项目概述:当飞书多维表格遇上Playwright自动化最近在帮一个做电商的朋友解决一个头疼事:他每天要手动去几个不同的电商平台后台,把各个商品的销量、库存、评价数抄下来,再填到飞书多维表格里做数据分析。这事儿听着就累&#x…

2026/7/6 22:15:01 阅读更多 →
6D 位姿估计 2024 综述:从 PnP 到 FoundationPose 的 3 大范式演进

6D 位姿估计 2024 综述:从 PnP 到 FoundationPose 的 3 大范式演进

6D位姿估计2024全景:从PnP到FoundationPose的技术跃迁与产业落地1. 技术演进的三次浪潮在增强现实导航手术机器人和智能仓储分拣系统中,精确的物体空间定位能力正成为关键瓶颈。6D位姿估计(3D位置3D旋转)技术历经三次方法论革新&a…

2026/7/6 22:13:00 阅读更多 →
Python Playwright自动化实战:从截图到表单提交的完整指南

Python Playwright自动化实战:从截图到表单提交的完整指南

1. 项目概述:为什么选择Playwright做自动化最近在帮团队处理一个重复性的网页操作任务,需要每天定时登录几个后台系统,截图保存数据报表,再提交一些固定的表单。手动操作不仅耗时,还容易出错。一开始我考虑过Selenium&…

2026/7/6 22:10:59 阅读更多 →
Windows 11/10 内置 OpenSSH 对比 PuTTY:连接阿里云 ECS 的2种密钥方案实测

Windows 11/10 内置 OpenSSH 对比 PuTTY:连接阿里云 ECS 的2种密钥方案实测

Windows 原生OpenSSH与PuTTY深度对比:连接阿里云ECS的密钥管理实战指南1. 密钥连接ECS的核心价值与工具选择每次输入冗长密码连接服务器的日子该结束了。在阿里云ECS的SSH连接场景中,密钥对认证早已成为安全运维的黄金标准。相比传统密码认证&#xff0c…

2026/7/6 22:10:59 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻