- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
导读
本文以开源仓库 wing 中 hangar 测试套件的一份编译快照asynchronous_model_implicit_await_in_functions.test.w_compile_tf-aws.md为切入点,深入解析 Wing 语言"inflight 函数内隐式 await"这一异步模型的核心语义。你将看到同一段 Wing 源码如何被编译为带await的 JavaScript inflight 闭包、preflight 合成逻辑以及完整的 AWS Terraform 资源配置,并结合编译器源码与 SDK 实现理解其底层原理。读完本文,你不仅能看懂 hangar 快照的每个细节,还能掌握 Wing 隐式异步等待的完整调用链。
快照文档是什么:hangar 测试套件与编译产物
这份快照文件位于 tools/hangar/snapshots/test_corpus/valid/asynchronous_model_implicit_await_in_functions.test.w_compile_tf-aws.md,它由 wing 仓库中的 hangar 工具链测试套件(tools/hangar/README.md)自动生成。hangar 是 Wing 的端到端(E2E)测试框架:它读取tests/目录下的.w测试文件,依次执行编译(compile)与运行(test)两类任务,并将每个目标(tf-aws、sim 等平台)的输出与预期快照做比对,从而保证编译器与 SDK 的每次改动都不会破坏既有行为。
快照文件名遵循严格约定:<测试文件名>_<任务类型>_<目标平台>.md。其中:
compile_tf-aws表示该快照记录的是针对tf-aws平台(通过 Terraform 部署到 AWS)的编译产物;test_sim表示该快照记录的是在模拟器(simulator)平台上的运行输出(见下文"运行验证"一节)。
快照头部以链接形式标注了被测源码的相对位置,其内容就是完整的编译产物。对应的 Wing 源码是 tests/valid/asynchronous_model_implicit_await_in_functions.test.w,全文只有十余行,却同时覆盖了 Queue、Function 资源定义与 inflight 异步调用语义,非常适合作为理解编译产物的最小可观测样例。
Wing 的异步编程模型:preflight 与 inflight
要理解这份快照,必须先建立 Wing 的双相位(phase)模型。Wing 将代码明确划分为两个执行阶段,官方入门文档 docs/by-example/14-async-functions.md 对此有清晰定义:
- Preflight(预检):在编译期运行一次的代码,负责生成云应用的基础设施配置——例如创建数据库、队列、存储桶、API 端点等资源;
- Inflight(飞行):在运行时执行的代码,实现应用的实际行为——例如处理 API 请求、消费队列消息。inflight 代码可以部署到 AWS Lambda、Azure Functions 等函数服务,也可以运行在容器、虚拟机甚至物理服务器上。
函数默认是 preflight 的;只有带inflight关键字、或定义在另一个 inflight 函数内部的函数才是 inflight 函数。测试文件中的两个cloud.Function都使用了inflight () => { ... }闭包语法,因此其函数体属于 inflight 相位,这正是编译产物中会出现async handle()的原因。
隐式 await:源码与编译产物的语义对照
先看被测 Wing 源码(tests/valid/asynchronous_model_implicit_await_in_functions.test.w):
bring cloud; let q = new cloud.Queue(); let strToStr = new cloud.Function(inflight () => { }) as "strToStr"; let func = new cloud.Function(inflight () => { strToStr.invoke("one"); log(strToStr.invoke("two") ?? "no response"); }) as "func";这里的关键细节在于func的 inflight 函数体内:
- 直接调用了
strToStr.invoke("one"),没有写await; - 将
strToStr.invoke("two")的结果通过?? "no response"与可选解包合并后传给log,同样没有写await。
而编译器为$Closure2生成的 inflight 代码(快照中inflight.$Closure2-1.cjs一节)是这样的:
async handle() { (await $strToStr.invoke("one")); console.log(((await $strToStr.invoke("two")) ?? "no response")); }两相对照,Wing 的隐式 await 语义一目了然:在 inflight 相位中调用异步方法时,编译器会自动在调用表达式前插入await,开发者无需(也通常不需要)手写await关键字。这正是该测试命名为asynchronous_model_implicit_await_in_functions(函数中的异步模型隐式等待)的原因。
编译器底层的 auto_await 机制
从源码层面看,这一行为由编译器 rust 实现中的 jsify(JavaScript 生成器)模块负责。在 packages/@winglang/wingc/src/jsify.rs 中可以看到:
let auto_await = match ctx.visit_ctx.current_phase() { Phase::Inflight => "await ", _ => "", };代码逻辑非常直白:当编译器处于inflight 相位时,函数调用表达式(packages/@winglang/wingc/src/jsify.rs 中auto_await被拼入生成的调用语句)会自动携带await前缀;而在 preflight 相位则不加。也就是说,auto_await是相位驱动的:同样的调用语句写法,在 preflight 与 inflight 两种语境下会被编译为不同的 JS 形态。
同时,packages/@winglang/wingc/src/jsify.rs 还提供了一道防线:如果在 preflight 相位中遇到对 inflight 值的引用,编译器会直接报错Cannot reference an inflight value from within a preflight expression,防止相位错乱。
返回值与可选类型的交互
快照中(await $strToStr.invoke("two")) ?? "no response"这行代码还演示了另一个语义点:cloud.Function.invoke的返回类型是Json | undefined(可选值)。Wing 对可选类型使用??(解包或取默认值,对应编译器中UnwrapOr二元运算符,见 packages/@winglang/wingc/src/jsify.rs),在 JS 层面直接映射为 nullish 合并运算符。由于strToStr的 handler 是空函数,运行时invoke返回undefined,因此??会兜底输出"no response"。
编译产物逐项解析
这份快照包含四个代码块,共同构成一次完整的 tf-aws 编译产物,下面逐项解读。
inflight.$Closure1-1.cjs 与 inflight.$Closure2-1.cjs:函数体与闭包封装
两个.cjs文件分别是两个 inflight 闭包的运行时实现,采用"模块工厂"模式:
module.exports = function({ $strToStr }) { class $Closure2 { ... async handle() { (await $strToStr.invoke("one")); console.log(((await $strToStr.invoke("two")) ?? "no response")); } } return $Closure2; }值得注意的细节:
$strToStr参数:$Closure2闭包通过构造参数捕获了外部资源strToStr(这正是 Wing 的捕获/提升机制,编译器只把实际用到的资源以参数形式注入闭包工厂)。而$Closure1(空函数)的工厂函数参数为空{ },因为它没有捕获任何外部资源。- 构造技巧:
constructor中把this.handle包装为可调用对象并Object.setPrototypeOf($obj, this),让闭包实例既可以被当作函数调用,又保留类方法能力。 handle统一为async:无论源码中是否出现异步调用,inflight handler 都以async handle()形式生成,为隐式await提供了合法的 JS 上下文。- 文件末尾带有
//# sourceMappingURL=...map,说明产物同时生成 source map,便于调试时还原到 Wing 源码。
preflight.cjs:应用合成逻辑
preflight.cjs是编译产物的"指挥中枢",负责在编译期构建整个应用树。它的关键结构:
- 通过
globalThis.$ClassFactory.new("...", ...)工厂方法实例化云资源——这正是 Wing 的平台抽象机制:同一个cloud.Queue/cloud.Function在不同平台(tf-aws、sim 等)会被替换为不同的底层实现; - 依次创建了
Queue(q)、strToStr函数和func函数三个资源,与源码一一对应; $Closure2的_toInflightType()与_liftMap揭示了提升(lift)信息:"handle"方法依赖[strToStr, ["invoke"]],即运行时需要以invoke操作访问strToStr。这份 lift 清单最终会驱动权限策略的生成(见下文的 IAM Policy);- 文件末尾调用
$APP.synth()完成合成,输出main.tf.json等 Terraform 配置。
main.tf.json:AWS 基础设施的完整画像
快照中最大的一块是main.tf.json,它展示了 Wing 把高层cloud.Function/cloud.Queue抽象映射到 AWS 资源的方式。逐资源梳理如下:
| Wing 抽象 | AWS 资源(main.tf.json 中的键) | 关键配置 |
|---|---|---|
strToStr函数 | aws_lambda_function.strToStr | runtime: nodejs20.x、memory_size: 1024、timeout: 60、architectures: ["arm64"]、handler: "index.handler" |
func函数 | aws_lambda_function.func | 同上,另注入环境变量FUNCTION_NAME_*指向strToStr的 ARN |
| 函数日志 | aws_cloudwatch_log_group.func_*/strToStr_* | 日志组名/aws/lambda/func-*,retention_in_days: 30 |
| 执行角色 | aws_iam_role.*_IamRole_* | assume_role_policy允许lambda.amazonaws.com承担 |
| 权限策略 | aws_iam_role_policy.func_IamRolePolicy_* | func的 IAM Policy 含lambda:InvokeFunction,Resource指向${aws_lambda_function.strToStr.arn}——这正是"func 调用 strToStr"所需的最小权限 |
| 基础执行角色 | aws_iam_role_policy_attachment.* | 挂载AWSLambdaBasicExecutionRole |
| 代码存储 | aws_s3_bucket.Code+ 两个aws_s3_object | 函数代码打包后以S3Object资产形式引用(<ASSET_KEY>/<ASSET_SOURCE>为占位符) |
q队列 | aws_sqs_queue.Queue | message_retention_seconds: 3600、visibility_timeout_seconds: 30 |
几个值得强调的实现事实:
- 权限与提升清单严格对应:
func的 IAM Policy 只授予lambda:InvokeFunction且资源限定为strToStr的 ARN(见 packages/@winglang/sdk/src/shared-aws/function.ts 中的lambda:InvokeFunction动作)。这正是 Wing 权限模型(显式提升 + 按需授权)在 tf-aws 平台的具体落地——你在源码中"用到了什么资源、什么操作",编译器就生成什么权限,无需手写 IAM。 - 环境变量桥接:
func的 Lambda 环境变量中出现了"FUNCTION_NAME_bc9a3a6d": "${aws_lambda_function.strToStr.arn}"。这是 Wing 在 inflight 运行时解析资源引用的机制——func在运行时通过这个环境变量拿到strToStr的 ARN 来发起调用;同时每个函数还注入WING_FUNCTION_NAME与WING_TARGET: "tf-aws"标识自身。 - 未使用的队列仍被合成:源码创建了
q = new cloud.Queue()却从未在 inflight 中使用,但 preflight 依然把它合成为 SQS 队列资源(aws_sqs_queue.Queue)。这与 cloud.Queue 的 inflight 方法(如 push)在未被提升时不产生额外权限策略的表现一致——资源存在但无对应调用权限。 - AWS Lambda 函数的默认参数:
memory_size: 1024与timeout: 60分别对应 packages/@winglang/sdk/src/cloud/function.ts 中FunctionProps.timeout(默认1m)与FunctionProps.memory(默认1024)的默认值。
invoke 的 SDK 抽象与 AWS 运行时实现
快照中$strToStr.invoke(...)在运行时究竟做了什么?这由 SDK 的两层代码共同回答。
第一层是抽象接口。cloud.Function的 inflight 客户端接口定义在 packages/@winglang/sdk/src/cloud/function.ts:
export interface IFunctionClient { invoke(payload?: Json): Promise<Json | undefined>; invokeAsync(payload?: Json): Promise<void>; }invoke同步等待函数返回结果;invokeAsync则"立即返回、后台执行"(对应FunctionInflightMethods枚举中的INVOKE与INVOKE_ASYNC,见 packages/@winglang/sdk/src/cloud/function.ts)。由于二者都返回Promise,所以编译器在 inflight 相位对它们的调用一律需要(隐式地)await。
第二层是平台实现。tf-aws 平台的实现在 packages/@winglang/sdk/src/shared-aws/function.inflight.ts:
invoke使用 AWS SDK v3 的InvokeCommand,通过FunctionName: this.functionArn指定目标函数;payload 为undefined时显式序列化为 JSONnull(避免 Lambda 收到空对象{});invokeAsync则额外设置InvocationType: "Event"走异步调用,并以response.StatusCode !== 202判断是否成功入队;- 同步调用失败时,
parseCommandOutput会解析 Lambda 返回的错误负载,并附上 CloudWatch 日志链接便于排查(packages/@winglang/sdk/src/shared-aws/function.inflight.ts)。
由此可以串起一条完整的调用链:Wing 源码中的strToStr.invoke("one")→ 编译器在 inflight 相位插入隐式await→ 运行时经环境变量中的 ARN 构造FunctionClient→ 调用 AWS Lambda 的InvokeCommand→ IAM 权限由编译期生成的lambda:InvokeFunction策略保证。
运行验证:sim 平台快照
除了 tf-aws 编译快照,hangar 还保存了同一测试在模拟器平台上的运行快照 tools/hangar/snapshots/test_corpus/valid/asynchronous_model_implicit_await_in_functions.test.w_test_sim.md。其内容是一份标准输出日志:
pass ─ asynchronous_model_implicit_await_in_functions.test.wsim (no tests) Tests (0) Snapshots 1 skipped Test Files 1 passed (1) Duration <DURATION>这份日志说明:该测试文件不包含test块("no tests"),因此在 sim 平台只完成编译与资源合成即视为通过;日志中的Duration <DURATION>是 hangar 快照的占位符(运行时长不纳入比对)。值得一提的是,虽然快照标题为test_sim,但 Wing 源码中的log(...)与invoke语义已在编译期被验证(类型检查 + jsify 成功),这正是隐式 await 语法被编译为合法async代码的直接证据。
对开发者而言,若想本地复现这份快照的行为,可以使用wing test(配合--platform sim)或wing it在 Wing 控制台中运行该.w文件(参见 docs/by-example/14-async-functions.md 中的wing it示例),模拟器会真实执行函数并打印日志;也可以参照 hangar 的运行方式,将目标设为 tf-aws 后对生成的main.tf.json执行terraform apply部署到真实 AWS 环境。
小结
通过asynchronous_model_implicit_await_in_functions这一份 hangar 编译快照,我们可以完整还原 Wing 隐式异步模型的四个层面:
- 语言层:inflight 函数中对异步方法的调用无需手写
await,由编译器自动补全; - 编译器层:
jsify.rs中的auto_await按当前相位(Phase::Inflight)决定是否注入await前缀,并禁止 preflight 引用 inflight 值; - SDK 层:
cloud.Function的invoke/invokeAsync统一返回Promise,平台实现(AWS LambdaInvokeCommand)与权限(lambda:InvokeFunction)均由编译产物自动生成; - 产物层:
preflight.cjs合成应用树,main.tf.json输出完整 AWS 资源配置,inflight.*.cjs携带闭包与提升清单。
这份快照同时是理解 Wing 权限模型(最小权限策略)、捕获/提升(lift)机制以及平台抽象(同一份源码多平台编译)的最佳样例。若想深入更多场景,可以继续阅读 tests/valid 目录下其他capture_*.test.w、inflight_*.test.w测试及其对应的 hangar 快照,它们共同构成了 Wing 异步语义的完整测试矩阵。
- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
相关推荐
Wing 语言 `util.sleep` 延迟执行解析:从 inflight 语义到 tf-aws 编译产物快照验证
Wing 语言 util.sleep 延迟执行解析:从 inflight 语义到 tf aws 编译产物快照验证 util.sleep 是 Wing 标准库中唯
编程语言编译器云原生语言运行时Wing 编译产物解剖:以 store.w 的 tf-aws 快照解读 preflight/inflight 与 lift 机制
Wing 编译产物解剖:以 store.w 的 tf aws 快照解读 preflight/inflight 与 lift 机制 导读 本文以 Wing 开源仓
编程语言编译器云原生语言运行时Wing 云编程语言实战:深入解析 cloud.Counter 的 dec() 递减语义与 tf-aws 编译产物
Wing 云编程语言实战:深入解析 cloud.Counter 的 dec 递减语义与 tf aws 编译产物 导读 cloud.Counter 是 Wing
编程语言编译器云原生语言运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考