ArkTS语言模块化加载异常常见问题

ArkTS 应用运行时出现模块化加载相关的异常报错提示,可能导致报错原因以及解决方法

"Cannot find dynamic-import module 'xxxx'"

报错表示当前加载的模块未被编译到当前应用包内

报错原因:

通过动态加载传入表达式作为入参时,模块路径参数书写有误。

  import(module).then(m=>{m.foo();}).catch((e: Error)=>{console.info(e.message)});

定位方法:

将待加载module路径信息打印出来,评估模块路径是否计算有误。

"Cannot find module 'xxxx' , which is application Entry Point"

报错表示拉起应用abc时,执行入口文件查找失败

报错原因:

应用拉起时,应用入口文件模块查找失败。

定位方法:

(1) 打开应用工程级编译构建文件: entry > src/main/module.json5

(OpenHarmony工程管理介绍) module.json5部分参数示例如下:

{
  "module": {
    "name": "entry",
    "type": "entry",
    ...
    "abilities": [
      {
        "name": "EntryAbility", // 模块名称
        "srcEntry": "./ets/entryability/EntryAbility.ts",  // 标明src目录相对工程根目录的相对路径
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

(2) 其中,"abilities":"srcEntry" 参数标记了该应用拉起的入口文件。如报错入口文件加载失败,请检查module.json5内的"srcEntry"参数是否书写正确。

"No export named 'xxxx' which exported by 'xxxx'"

报错表示加载应用hap或har包内so时,该模块内未查找到特定对象

报错原因:

ets在模块化静态编译阶段,会预解析模块间依赖关系。ets文件内的导入变量名书写错误时,ide编译器与应用编译阶段均会报错提示。但目前对于应用内native C++模块的依赖关系检测会在运行阶段。

定位方法:

检查应用内so是否存在报错提示的导出变量,与加载应用内so导入变量处进行比较,不一致则适配修改。

ArkTS/Ts/Js加载so失败,表现行为是怎么样的

加载so失败后,不显式抛出加载失败的js异常。开发者可以通过导出对象是否为undefined判断so的加载状态。

加载失败具体表现

加载类型 ts/js模块 系统库so或应用so
静态加载 虚拟机自动抛出异常,进程退出 无异常抛出,加载到的对象为undefined
动态加载 不主动抛出异常,走到reject分支,开发者可以调用catch方法来捕获这个错误 不主动抛出异常,依然进入resolve分支,开发者可以在resolve分支中检查模块导出变量是否为undefined

示例1:系统库so或应用so静态加载失败

import testNapi from 'libentry.so'

if (testNapi == undefined) {
  console.error('load libentry.so failed.');
}

执行结果

load libentry.so failed.

示例2:系统库so或应用so动态加载失败

import('libentry.so')
  .then(m => {
    if (typeof m.default === 'undefined') {
      console.warn(`load libentry.so failed.`);
    } else {
      console.info('load libentry.so success:', m);
    }
    return m;
  })
  .catch((e: Error) => {
    console.error('load libentry.so error:', e);
  });

执行结果

load libentry.so failed.

so加载失败可能的原因、定位方式以及解决方法

参考(Node-API常见问题)文档

模块间循环依赖导致运行时未初始化异常问题定位

模块间的循环依赖往往是存在这种文件依赖情况:

// file1
  export {b} from './file2'
  export {a} from './A'

// file2
  import {a} from './file1'
  export let b:number = a;

// A
  export let a:number = 50;

如上代码会报错:

Error message:a is not initialized

但如果仅仅是更换file1的加载顺序:

// file1
  export {a} from './A'
  export {b} from './file2'
// file2
  import {a} from './file1'
  export let b:number = a;
// A
  export let a:number = 50;

以上代码就不会报错。此时执行顺序:file1加载A, A文件无依赖;返回file1继续加载file2, file2文件加载file1时,变量a文件所在的A已经执行完成,因此正常加载。 开发者需注意,模块化编译采用的是深度遍历加载。

循环依赖的解决方法:

安全规则@security/no-cycle

ArkTS 符号未初始化报错场景示例

ArkTS语言规范是基于ECMAScript规范的子集,根据语言规范,当访问一个还未完成初始化的符号时,运行时会抛出异常,为便于开发者定位解决源码问题,提供以下常见报错场景示例用于参考。

const/let声明前访问

console.log(a); // 报错信息:Variable 'a' is used before being assigned.
console.log(b); // 报错信息:Variable 'b' is used before being assigned.

let a = '1';
const b = '2';

正例

let a = '1';
const b = '2';

console.log(a);
console.log(b);

class声明前实例化

let a = new A(); // 报错信息:Class 'A' used before its declaration.

class A {}

正例

class A {}

let a = new A();

class声明前访问其静态属性

let a = A.a; // 报错信息:Class 'A' used before its declaration.

class A {
  static a = 1;
}

正例

class A {
  static a = 1;
}

let a = A.a;

在函数中访问未提前声明的let和const变量

foo(); // 报错信息:Error message:a is not initialized

let a = 1;
const b = 2;

function foo() {
  let v = a + b;
}

正例

let a = 1;
const b = 2;

function foo() {
  let v = a + b;
}

foo();

在函数中访问未提前声明的类的静态属性

foo(); // 报错信息:Error message:A is not initialized

class A {
  static a = 1;
}

function foo() {
  let v = A.a;
  let w = new A();
}

正例

class A {
  static a = 1;
}

function foo() {
  let v = A.a;
  let w = new A();
}

foo();

模块间循环依赖 - const/let

// module1.ets
import { a, b } from './module2'

export let i = 1;
export let m = a;
export const j = 2;
export const n = b;

// ---------------------

// module2.ets
import { i, j } from './module1'

export let a = i; // 报错信息:Error message:i is not initialized
export const b = j; // 报错信息:Error message:j is not initialized

解决方法

详见循环依赖的解决方法

模块间循环依赖 - class

// class1.ets
import { b } from './class2'

export class A {
  static a = b;
}

// ---------------------

// class2.ets
import { A } from './class1'
export let b = 1;

const i = A.a; // 报错信息:Error message:A is not initialized
const j = new A(); // 报错信息:Error message:A is not initialized

解决方法

详见循环依赖的解决方法