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