await 处理自定义 thenable 时 v8 不触发内联缓存(ic)优化,因其执行走 promise 状态机与 thenable 适配协议,绕开 ic 作用范围;真实流程包括 getthen 通用查找、then 函数验证与动态调用,全程不依赖隐藏类与属性偏移缓存。

await 后面跟自定义 thenable 对象时,V8 不会触发内联缓存(IC)对 .then 方法的优化,更不存在所谓“IC 动态优化机制”——因为这个场景根本绕开了 IC 的作用范围。
内联缓存只在直接、稳定、静态可分析的属性访问或方法调用中生效,比如 obj.x、arr.push()。而 await x 的执行逻辑由 V8 的 Promise 状态机与 thenable 适配协议驱动,全程不走普通方法调用路径,也不依赖隐藏类 + 属性偏移缓存。
await 处理 thenable 的真实流程(非 IC 路径)
当 x 是一个自定义 thenable(即有 then 方法但不是原生 Promise 实例)时,V8 按规范执行以下步骤:
- 检查
x是否为null或undefined→ 抛错 - 获取
x.then:这里看似是属性读取,但await的实现底层调用的是内部抽象操作GetThen,它强制走通用属性查找,跳过所有 IC 优化 - 验证
then是否为函数:若不是,抛TypeError - 以
x为this,调用then(resolve, reject):该调用走的是动态函数调用路径,不绑定固定隐藏类,IC 无法记录有效类型信息
✅ 这个过程完全脱离 JIT 编译器对“热点属性访问”的内联缓存建模。V8 不会为
x.then在await上下文中维护任何 IC stub。
为什么你感觉“有时快、有时慢”?真相是:
- 如果
x总是同一构造函数创建的 thenable(如new MyThenable()),且then始终是同一个函数引用,V8 可能在 call-site 层面做简单内联(inlining)或类型反馈缓存(type feedback),但这属于函数调用优化,和“内联缓存属性访问”无关 - 若
x来自不同构造器、或then是每次新建的箭头函数,V8 会标记该await位置为 megamorphic call site,后续直接放弃优化,走解释器慢路径 -
console.time()测出的波动,往往来自 GC、JIT 编译延迟、或then函数体本身的开销,而非 IC 命中/未命中
开发者真正可控的优化点
- ✅ 避免在
await前动态构造 thenable:如await { then(cb) { /*...*/ } }—— 每次都生成新对象,无隐藏类,IC 和类型反馈全失效 - ✅ 复用 thenable 实例(谨慎):若业务允许,缓存
const t = new Thenable(...),但注意状态污染风险 - ✅ 优先用原生 Promise:
await Promise.resolve(x)比await x(x 是 thenable)快,因为 V8 对Promise.resolve和await Promise有专用快速路径,含预编译状态机和零 IC 干扰 - ❌ 不要试图“稳定 thenable 的隐藏类”来优化
await:then是方法,不是数据属性;IC 不管方法调用的多态性,只管obj.prop这种读取
总结一句话
await 遇到自定义 thenable,V8 的处理逻辑在语义层就放弃了内联缓存——它不是“没优化好”,而是设计上就不适用。真正的性能瓶颈从来不在 x.then 的读取速度,而在 then 回调的执行质量、Promise 链深度、以及是否意外触发 microtask 队列膨胀。把精力放在减少 thenable 创建、扁平化异步流、用 Promise.resolve 替代裸 thenable,比琢磨“IC 如何优化 await”实在得多。











