升级ECMAScript至es2018+后fakeAsync测试失败问题排查
你的测试失败本质是ES2018+环境下的原生异步特性,与Angular fakeAsync依赖的Zone.js拦截机制之间的适配问题,具体涉及两个关键变化:
1. setTimeout返回值的Thenable特性(ES2018+宿主环境支持)
在ES2017及更早的规范中,setTimeout仅返回数字类型的定时器ID,无法直接被await。你的代码必然是手动将其包装为Promise(比如await new Promise(resolve => setTimeout(resolve, 300))),这种情况下,Zone.js可以完全拦截这个手动创建的Promise,fakeAsync的tick()方法能正确模拟时间流逝,触发Promise resolve后再执行后续断言。
而ES2018之后,现代浏览器和Node.js的setTimeout返回的Timeout对象实现了Thenable接口(自带then方法)。TypeScript在ES2018+编译目标下,允许直接await setTimeout(...)无需手动包装Promise。此时这个Promise由宿主环境原生生成,Zone.js的fakeAsync机制并未对这类原生Thenable进行拦截,导致tick()无法识别并推进它的resolve流程,最终verifyModalDestroyed()会在定时器逻辑完成前提前执行,断言失败。
2. TypeScript编译输出的差异
当编译目标设为ES2016时,TypeScript会将async/await转译为基于Promise的兼容代码,所有异步操作都走Zone.js能拦截的模拟Promise链;而ES2018+编译目标下,TypeScript直接输出原生async/await语法,依赖宿主环境的原生异步调度,这就绕开了Zone.js对部分异步操作的追踪能力。
- 保持手动包装Promise:不要直接
await setTimeout(...),继续使用await new Promise(resolve => setTimeout(resolve, 300)),确保Zone.js能拦截到这个Promise。 - 升级Zone.js版本:新版本的Zone.js已修复对原生Thenable类型(如Timeout对象)的拦截问题,升级后即可正常使用
await setTimeout(...)配合fakeAsync测试。
内容的提问来源于stack exchange,提问作者Cody Pritchard

