1# ArkTS语言模块化加载异常常见问题 2 3<!--Kit: ArkTS--> 4<!--Subsystem: ArkCompiler--> 5<!--Owner: @shilei123--> 6<!--Designer: @shilei123;@yao_dashuai--> 7<!--Tester: @kir175; @zsw_zhushiwei--> 8<!--Adviser: @huipeizi--> 9 10 11## ArkTS 应用运行时出现模块化加载相关的异常报错提示,可能导致报错原因以及解决方法 12### "Cannot find dynamic-import module 'xxxx'" 13 14报错表示当前加载的模块未被编译到当前应用包内 15 16**报错原因:** 17 18通过动态加载传入表达式作为入参时,模块路径参数书写有误。 19``` typescript 20 import(module).then(m=>{m.foo();}).catch((e: Error)=>{console.error(e.message)}); 21``` 22 23**定位方法:** 24 25打印待加载模块的路径信息,评估模块路径是否计算有误。 26 27### "Cannot find module 'xxxx' , which is application Entry Point" 28报错表示拉起应用abc时,应用入口文件查找失败 29 30**报错原因:** 31 32在应用拉起时,查找应用入口文件模块失败。 33 34**定位方法:** 35 36(1) 打开应用工程级编译构建文件: entry > src/main/module.json5 37 38([OpenHarmony工程管理介绍](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V3/ohos-project-overview-0000001218440650-V3)) 39module.json5部分参数示例如下: 40``` 41{ 42 "module": { 43 "name": "entry", 44 "type": "entry", 45 ... 46 "abilities": [ 47 { 48 "name": "EntryAbility", // 模块名称 49 "srcEntry": "./ets/entryability/EntryAbility.ts", // 标明src目录相对工程根目录的相对路径 50 "description": "$string:EntryAbility_desc", 51 "icon": "$media:icon", 52 "label": "$string:EntryAbility_label", 53 "startWindowIcon": "$media:icon", 54 "startWindowBackground": "$color:start_window_background", 55 "exported": true, 56 "skills": [ 57 { 58 "entities": [ 59 "entity.system.home" 60 ], 61 "actions": [ 62 "action.system.home" 63 ] 64 } 65 ] 66 } 67 ] 68 } 69} 70``` 71(2) 其中,"abilities":"srcEntry" 参数标记了该应用拉起的入口文件。如报错入口文件加载失败,请检查module.json5内的"srcEntry"参数是否书写正确。 72 73### "No export named 'xxxx' which exported by 'xxxx'" 74报错表示加载应用hap或har包内so时,该模块内未查找到特定对象 75 76**报错原因:** 77 78ets在模块化静态编译阶段,会预解析模块间的依赖关系。ets文件内的导入变量名书写错误时,ide编译器与应用编译阶段均会报错提示。但目前对于应用内native C++模块的依赖关系检测会在运行阶段。 79 80**定位方法:** 81 82检查应用内so是否存在报错提示的导出变量,并与加载应用内so的导入变量进行比较。如果不一致,则进行适配修改。 83 84 85## ArkTS/Ts/Js加载so失败,表现行为是怎么样的 86 87加载so失败后,不显式抛出加载失败的js异常。开发者可以通过导出对象是否为undefined判断so的加载状态。 88 89**加载失败具体表现** 90 91| 加载类型 | ts/js模块 | 系统库so或应用so | 92| -------- | -------- | -------- | 93| 静态加载 | 虚拟机自动抛出异常,进程退出 | 无异常抛出,加载到的对象为undefined | 94| 动态加载 | 不主动抛出异常,走到reject分支,开发者可以调用catch方法来捕获这个错误 | 不主动抛出异常,依然进入resolve分支,开发者可以在resolve分支中检查模块导出变量是否为undefined | 95 96**示例1:系统库so或应用so静态加载失败** 97 98``` 99import testNapi from 'libentry.so' 100 101if (testNapi == undefined) { 102 console.error('load libentry.so failed.'); 103} 104``` 105 106执行结果 107``` 108load libentry.so failed. 109``` 110 111**示例2:系统库so或应用so动态加载失败** 112 113``` 114import('libentry.so') 115 .then(m => { 116 if (typeof m.default === 'undefined') { 117 console.warn(`load libentry.so failed.`); 118 } else { 119 console.info('load libentry.so success:', m); 120 } 121 return m; 122 }) 123 .catch((e: Error) => { 124 console.error('load libentry.so error:', e); 125 }); 126``` 127 128执行结果 129``` 130load libentry.so failed. 131``` 132 133**so加载失败可能的原因、定位方式以及解决方法** 134 135参考([Node-API常见问题](https://gitcode.com/openharmony/docs/blob/master/zh-cn/application-dev/napi/use-napi-faqs.md))文档 136 137 138## 模块间循环依赖导致运行时未初始化异常问题定位 139### 循环依赖问题定位方法 140报错xxx is not initialized不一定是循环依赖问题,需要打开开关进一步确认: 141``` 142打开option: hdc shell param set persist.ark.properties 0x2000105c 143打开后必须重启机器:hdc shell reboot 144如果需要关闭option: hdc shell param set persist.ark.properties 0x0000105c 145关闭后必须重启机器:hdc shell reboot 146``` 147开关打开后复现报错就是第一现场报错,解决即可。如果报错仍是xxx is not initialized, 就是文件存在循环依赖。 148 149### 循环依赖原理 1501. 模块加载顺序: 151根据ECMA规范,模块的执行顺序是深度遍历加载。 152假设应用存在加载链路A->B->C,那么ArkTs模块化会先执行C文件,再执行B文件,最后执行A文件,执行顺序为C->B->A。 1532. 循环依赖: 154如果应用存在加载链路A->B->A,根据深度遍历执行顺序,执行流程会先标记A的状态为加载中,然后去加载B,标记B的状态为加载中,然后去加载A,由于A文件已经标记加载中,根据规范定义,识别到加载中模块会直接返回,就会先执行B文件。 155**为什么有的循环依赖没有影响,有的就会产生crash?** 156由上面的叙述可知,B文件虽然依赖A文件变量,但是B文件先执行,如果B文件导入的A文件变量没有在全局或者类静态中被使用,B文件将正常执行。如果B文件在全局或者实例化某个类等其他方法,导致文件执行时就会用到A的变量,就会产生xxx is not initialized的crash,即循环依赖导致变量未被初始化。 157 158示例: 159``` typescript 160// A.ets 161import { Animal } from './B' 162export let a = "this is A"; 163export function A() { 164 return new Animal; 165} 166 167// --------------------- 168 169// B.ets 170import { a } from './A' 171export class Animal { 172 static { 173 console.log("this is in class"); 174 let str = a; // 报错信息:a is not initialized 175 } 176} 177``` 178**正例** 179 180``` typescript 181// B.ets 182import { a } from './A' 183export class Animal { 184 static { 185 console.log("this is in class"); 186 } 187 str = a; // 修改点 188} 189``` 190 191### 循环依赖的解决方法: 192[安全规则@security/no-cycle](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide_no-cycle-V5) 193 194 195## ArkTS 符号未初始化报错场景示例 196 197ArkTS语言规范是基于ECMAScript规范的子集,根据语言规范,当访问一个还未完成初始化的符号时,运行时会抛出异常,为便于开发者定位解决源码问题,提供以下常见报错场景示例用于参考。 198 199### const/let声明前访问 200 201``` typescript 202console.log(a); // 报错信息:Variable 'a' is used before being assigned. 203console.log(b); // 报错信息:Variable 'b' is used before being assigned. 204 205let a = '1'; 206const b = '2'; 207``` 208**正例** 209 210``` 211let a = '1'; 212const b = '2'; 213 214console.log(a); 215console.log(b); 216``` 217 218 219### class声明前实例化 220 221``` typescript 222let a = new A(); // 报错信息:Class 'A' used before its declaration. 223 224class A {} 225``` 226 227**正例** 228 229``` 230class A {} 231 232let a = new A(); 233``` 234 235### class声明前访问其静态属性 236 237``` typescript 238let a = A.a; // 报错信息:Class 'A' used before its declaration. 239 240class A { 241 static a = 1; 242} 243``` 244 245**正例** 246 247``` 248class A { 249 static a = 1; 250} 251 252let a = A.a; 253``` 254 255### 在函数中访问未提前声明的let和const变量 256 257``` typescript 258foo(); // 报错信息:Error message:a is not initialized 259 260let a = 1; 261const b = 2; 262 263function foo() { 264 let v = a + b; 265} 266``` 267 268**正例** 269 270``` 271let a = 1; 272const b = 2; 273 274function foo() { 275 let v = a + b; 276} 277 278foo(); 279``` 280 281### 在函数中访问未提前声明的类的静态属性 282 283``` typescript 284foo(); // 报错信息:Error message:A is not initialized 285 286class A { 287 static a = 1; 288} 289 290function foo() { 291 let v = A.a; 292 let w = new A(); 293} 294``` 295 296**正例** 297 298``` 299class A { 300 static a = 1; 301} 302 303function foo() { 304 let v = A.a; 305 let w = new A(); 306} 307 308foo(); 309``` 310 311### 模块间循环依赖 - const/let 312 313``` typescript 314// module1.ets 315import { a, b } from './module2' 316 317export let i = 1; 318export let m = a; 319export const j = 2; 320export const n = b; 321 322// --------------------- 323 324// module2.ets 325import { i, j } from './module1' 326 327export let a = i; // 报错信息:Error message:i is not initialized 328export const b = j; // 报错信息:Error message:j is not initialized 329``` 330 331**解决方法** 332 333详见[循环依赖的解决方法](#循环依赖的解决方法) 334 335 336### 模块间循环依赖 - class 337 338``` typescript 339// class1.ets 340import { b } from './class2' 341 342export class A { 343 static a = b; 344} 345 346// --------------------- 347 348// class2.ets 349import { A } from './class1' 350export let b = 1; 351 352const i = A.a; // 报错信息:Error message:A is not initialized 353const j = new A(); // 报错信息:Error message:A is not initialized 354``` 355 356**解决方法** 357 358详见[循环依赖的解决方法](#循环依赖的解决方法)