)
【声明】本博客所有内容均为个人业余时间创作所述技术案例均来自公开开源项目如GithubApache基金会不涉及任何企业机密或未公开技术如有侵权请联系删除标题140、【Agent】【OpenCode】启动分析await背景上篇 blog【Agent】【OpenCode】启动分析类型断言分析了 TypeScript 的断言和 C 语言的类型断言assert的区别TypeScript 的类型断言是告诉编译器相信我这个值不用检查就是这个类型但如果骗了编译器由于 JS 不会作任何运行时检查此时运行时可能崩溃而 C 语言的断言是验证某个条件是否为真为假则终止程序在 OpenCode 这里因为yargs结合 middleware 已经放在校验之后已经保证了值的合法性但 TypeScript 的类型系统无法理解yargs的choices约束只知道opt.logLevel是string | undefine类型所以这里开发者用as来弥合运行时保证和静态类型之间的鸿沟接着分析了as断言是 TypeScript 独有的语法JavaScript 没有类型断言因为 JS 是动态类型语言根本没有编译期类型检查这一步以及如果没有as断言的话OpenCode 的Log.Init一定会报错下面继续分析OpenCode下面看下这里的异步async和await机制这里的await的字面意思是等待在 JavaScript / TypeScript 中它的准确含义是暂停当前异步函数的执行知道后面的 Promise 完成resolve 或 reject然后恢复执行并获取结果awaitLog.init({...})在await等待中Log.init()返回一个 Promise日志系统的初始化是异步操作可能涉及文件 IO网络连接等await让当前函数暂停在这里不会继续执行后续代码当Log.init的 Promise resolve 后程序恢复执行下一行代码如果 Promise则会抛出异常可以被 try catch 捕获另外这里必须用await因为日志系统必须先初始化完成后续代码才能安全地写日志如果不await// ❌ 危险init 还没完成下面就开始打日志了Log.init({...})Log.info(server started)// 可能写入失败、丢失、或触发未初始化错误// ✅ 安全确保日志系统就绪后才继续awaitLog.init({...})Log.info(server started)// 日志系统已准备好可靠写入另外await只能在async函数中使用所以可以看到 middleware 实际上是异步的如果在非async函数中使用awaitTypeScript 和 JavaScript 都会直接报语法错误另外await和then的功能等价但await能够让异步代码读起来像同步代码比如// .then() 写法回调嵌套逻辑分散Log.init({...}).then((){Log.info(server started)})// await 写法线性阅读逻辑集中awaitLog.init({...})Log.info(server started)所以 OpenCode 这里的await表示日志初始化是异步的必须等待其真正完成后才允许程序继续往下走保障后续所有日志操作的可靠性此外await后面可以跟任何值不限于async函数的返回值await的本质是一个通用解包操作符其规则非常简单下面看await的三种行为await后面的值行为Promise暂停等待 resolve返回 resolve 值非 Promise 值stringnumberobject 等不暂停直接将该值原样返回thenable 对象有.then()方法的普通对象当作类 Promise 处理暂停并调用其.then()举例如下asyncfunctiondemo(){// ✅ 跟 async 函数返回 Promise—— 最常见constdataawaitfetch(/api)// ✅ 跟普通同步函数 —— 完全合法只是没必要constlenawaithello.length// 5不暂停constnumawaitMath.random()// 0.723...不暂停// ✅ 跟字面量 —— 合法但无意义constxawait42// 42constsawaithello// hello// ✅ 跟变量运行时才知道是不是 Promiseconstvalue:string|PromisestringgetValue()constresultawaitvalue// 如果是 Promise 就等否则直接拿值}这里之所以允许await非 Promise是可以的设计决策因为多态性比如当写一个接收参数的函数时开发者往往不知道传入的是同步值还是异步值// 无论 config 是同步对象还是 Promise这段代码都能正确工作asyncfunctioninit(config:Config|PromiseConfig){constcawaitconfig// ← 统一处理无需 if/else 判断startServer(c)}如果await只接受 Promise开发者就必须先检查再决定要不要await代码就会变得极其繁琐而允许await非 Promise 值就能让同一套代码可以同时兼容同步和异步输入这在库开发和重构过程中极为重要所以await的限制不在于后面跟什么而在于前面在哪里比如❌不能在普通函数中使用❌ 不能在模块顶层使用除非是 ES2022 Top-Level Await✅只能在 async 函数或支持 top-level await 的模块中使用最后总结一下await后面可以是任何东西它是 Promise 时就等待不是时就透传这个设计能让开发者无需关心值的同步和异步身份用统一的语法安全地处理两种情况而真正被限制的永远是await自身所处的位置而不是它操作的对象OK本篇先到这里如有疑问欢迎评论区留言讨论祝各位功力大涨技术更上一层楼更多内容见下篇 blog