在这里插入图片描述

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的App!
📌 关注本专栏《零基础学鸿蒙开发》,一起变强!
每一节内容我都会持续更新,配图+代码+解释全都有,欢迎点个关注,不走丢,我是小白酷爱学习,我们一起上路 🚀

前言

在 ArkTS 里写异步代码,很多人处理错误的方式大概是这样的:

try {
  await fs.readFile(path);
} catch (err) {
  console.error('读取失败');
}

出了问题,日志里只有"读取失败"四个字。排查时根本不知道是路径不存在、权限被拒,还是参数写错了。改成打印 err 本身也解决不了问题——很多开发者发现打出来的是 [object Object],或者信息残缺。

问题出在没有正确使用 BusinessError 类型。这篇文章从这个问题出发,整理一个可以在实际项目里直接复用的错误日志模板。

一、HarmonyOS 里的错误到底长什么样

HarmonyOS ArkTS 的系统 API 在运行失败时,抛出的是 BusinessError 类型的对象,而不是普通的 Error。

BusinessError 定义在 @ohos.base 模块中,继承自 JavaScript 的 Error,起始版本是 API 7:

属性类型说明起始版本
codenumber系统错误码,每个 Kit 都有独立的错误码体系API 7
messagestring错误描述字符串API 7
dataT(泛型,可选)附加错误数据API 9

导入方式:

import { BusinessError } from '@ohos.base';

code 才是排查问题的关键。以文件管理为例,13900002 表示文件不存在,13900012 表示权限被拒——两种情况处理逻辑完全不同,但如果只打印"操作失败",完全无从区分。

二、为什么 console.error(err) 打不出有效信息

BusinessError 继承自 Error,但直接把对象传给 console.error() 时,ArkTS runtime 对它的序列化结果并不可靠——有时只输出 Error: message,code 字段丢失;有时输出 [object Object],信息为空。

原因在于 code 是自定义属性,不在标准 Error 的可枚举范围内,依赖 console.error(err) 的隐式序列化无法保证它出现在日志里。

正确做法是显式访问 err.code 和 err.message。

三、通用日志模板

一个在实际项目中有意义的 catch 块,至少要记录四项内容:

操作名称    ← 当前在做什么("读取用户配置文件",不是"文件操作")
error.code  ← 系统错误码,用于查文档、区分原因
error.message ← 错误描述,辅助阅读
必要上下文  ← 操作的关键入参,例如路径、abilityName、bundleName

将这四项封装成一个工具函数:

import { BusinessError } from '@ohos.base';

/**
 * 统一记录 BusinessError。
 * @param operation 当前操作的描述,建议精确到"动词+操作对象"
 * @param err       catch 块捕获的 BusinessError
 * @param context   与操作直接相关的关键参数,便于复现问题
 */
function logBusinessError(
  operation: string,
  err: BusinessError,
  context?: Record<string, string | number | boolean>
): void {
  const contextStr = context ? JSON.stringify(context) : '无';
  console.error(
    `[ERROR] 操作: ${operation} | 错误码: ${err.code} | 信息: ${err.message} | 上下文: ${contextStr}`
  );
}

这里把四项信息拼成一行,方便在 DevEco Studio 的 HiLog 或日志过滤器里直接搜索 [ERROR] 快速定位。

四、用文件 API 主动触发一次错误

下面用 @ohos.file.fs 的 readFile 接口,故意读取一个不存在的文件,观察 BusinessError 的实际内容。

fs.readFile 要求使用应用沙箱路径,在 Stage 模型中通过 context.filesDir 获取。

前置条件:

  • Stage 模型
  • API Level ≥ 9
  • 无需额外权限(访问应用自身沙箱目录)
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
import common from '@ohos.app.ability.common';

// 在 UIAbility 或 Page 中获取 context
// 这里假设已经通过组件的 getContext() 拿到 context
async function triggerFileReadError(context: common.UIAbilityContext): Promise<void> {
  // 构造一个确定不存在的路径
  const fakePath = context.filesDir + '/this_file_does_not_exist.txt';

  try {
    // fs.readFile 在 API 9 引入,返回 Promise<ArrayBuffer>
    await fs.readFile(fakePath);
  } catch (e) {
    const err = e as BusinessError;
    logBusinessError(
      '读取用户本地配置文件',
      err,
      { path: fakePath }
    );
  }
}

根据官方文件管理错误码说明,读取不存在的路径时,err.code 应为 13900002,err.message 为 No such file or directory。

日志输出结构将类似:

[ERROR] 操作: 读取用户本地配置文件 | 错误码: 13900002 | 信息: No such file or directory | 上下文: 
{"path":"/data/storage/el2/base/files/this_file_does_not_exist.txt"}

五、用 Ability API 主动触发一次错误

UIAbilityContext.startAbility 在目标 Ability 不存在时会抛出 BusinessError,错误码为 16000001。

import common from '@ohos.app.ability.common';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

async function triggerAbilityStartError(context: common.UIAbilityContext): Promise<void> {
  const want: Want = {
    bundleName: 'com.example.nonexistent',
    abilityName: 'FakeAbility'
  };

  try {
    // startAbility 在 API 9 引入,Stage 模型使用
    await context.startAbility(want);
  } catch (e) {
    const err = e as BusinessError;
    logBusinessError(
      '启动目标页面 Ability',
      err,
      {
        bundleName: want.bundleName ?? '',
        abilityName: want.abilityName ?? ''
      }
    );
  }
}

根据官方 Ability 错误码说明,16000001 对应"指定的 Ability 不存在",排查方向是核对 bundleName 和 abilityName 是否正确。把这两个值写入 context,排查时不需要去翻业务代码就能知道当时传的是什么。

六、几个容易忽略的细节

1. 类型断言不能省

catch (e) 捕获到的 e 在 ArkTS 中是 unknown 类型,不是 BusinessError。直接访问 e.code 会在编译阶段报错。必须先做类型断言:

const err = e as BusinessError;

这里是断言,不是类型保护。如果某个地方真的有可能抛出非 BusinessError 的异常(比如自己写的逻辑错误),断言会让 err.code 拿到 undefined。想防御这种情况,可以加一个简单判断:

if (typeof (e as BusinessError).code === 'number') {
  const err = e as BusinessError;
  logBusinessError(operation, err, context);
} else {
  console.error(`[ERROR] 操作: ${operation} | 非 BusinessError 异常: ${String(e)}`);
}

2. error.message 是英文,不是中文提示

BusinessError.message 是系统内部的英文描述,例如 No such file or directory。它的用途是辅助开发者查文档,不能直接展示给用户。面向用户的错误提示需要根据 err.code 自行映射。

3. context 里只放关键入参,不要放敏感信息

日志里的 context 是为了复现问题,记录路径、Ability 名、操作类型这类信息即可。用户 ID、token、密码不应出现在日志里。

4. data 属性不是所有错误都有

BusinessError.data 是泛型可选属性,起始版本 API 9,但大多数系统 API 的错误对象不会填充这个字段。不要把模板里默认依赖 data,优先从 code 和 message 入手。

七、排查问题时的顺序

当看到日志中的 BusinessError 后,建议按下面的顺序来定位:

  1. 先看 code:找到对应 Kit 的错误码文档(文件管理 / Ability / 网络 / 媒体……),确认这个码的含义和官方建议的排查方向。
  2. 再看 context:日志里记录的入参,确认调用时的实际值是否符合预期。
  3. 检查权限:如果 code 是 13900012(Permission denied)或 16000004(无法启动不可见组件),先查 module.json5 里的 requestPermissions 配置。
  4. 检查路径格式:文件操作的路径必须是应用沙箱路径,不能是 URI 或相对路径。
  5. 检查 API Level:确认使用的接口在当前设备的 HarmonyOS 版本上是否已支持。
  6. 检查 Stage 模型限制:@ohos.file.fs、UIAbilityContext.startAbility 等接口仅支持 Stage 模型,FA 模型下的调用方式不同。

开发经验总结

  • catch (e) 里必须访问 err.code 和 err.message,直接序列化 err 对象无法保证 code 出现在日志里。
  • 把"操作名称 + 错误码 + 错误信息 + 关键入参"固定为一行结构化日志,是让错误可以被快速复现的最低要求。
  • BusinessError.message 是英文系统描述,面向用户的提示需要根据 code 另行处理。
  • 日志里的上下文只记录排查必须的入参,不记录任何敏感字段。
  • err.data 是可选属性,实际使用中多数系统 API 的错误不填充它,不要把模板逻辑依赖在这个字段上。

如果你的项目里有多个地方调用文件操作或 Ability 启动,可以把 logBusinessError 抽进一个公共工具模块,统一日志格式。维护期间过滤 [ERROR] 标签时,所有 BusinessError 就都能被统一捕获,不会散落在各处业务代码里。

❤️ 如果本文帮到了你…

  • 请点个赞,让我知道你还在坚持阅读技术长文!
  • 请收藏本文,因为你以后一定还会用上!
  • 如果你在学习过程中遇到bug,请留言,我帮你踩坑!
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐