You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何处理拒绝Promise的Deno测试会出现不稳定(Flaky)问题?

Deno文件操作测试中的竞态问题排查与解决

问题场景

运行以下测试代码:

const file = "exp.txt";

Deno.test("handle rejected promise", async (t) => {
  try {
    await Deno.stat(file);
  } catch (e) {
    if (e instanceof Deno.errors.NotFound) {
      await Deno.writeTextFile(file, "some text");
    } else {
      throw e;
    }
  } finally {
    await Deno.remove(file);
  }
});
  • 使用命令 deno test --allow-read --allow-write exp.test.ts 运行时,测试成功。
  • 添加 --trace-ops 选项后,执行 deno test --allow-read --allow-write --trace-ops exp.test.ts,测试失败并抛出错误:

./exp.test.ts (uncaught error)
error: (in promise) NotFound: The system cannot find the file specified. (os error 2), stat 'exp.txt'
This error was not caught from a test and caused the test runner to fail on the referenced module.
It most likely originated from a dangling promise, event/timeout handler or top-level code.

此外,更复杂的同类代码即使不添加--trace-ops也会失败,表现出竞态条件或超时相关的不稳定问题。当前使用Deno 1.28.3,多个1.2x版本均存在此不稳定现象,但1.28.3之前版本无该问题。注:这种通过stat+捕获NotFound错误检查文件存在性的方式,是Deno官方文档推荐的实现逻辑。

核心原因

问题本质是测试上下文销毁与异步文件操作的时序竞态:

  • 当启用--trace-ops时,测试 runner 的执行节奏被改变,finally块中的Deno.remove可能先于catch块中的Deno.writeTextFile完成,或者测试函数已执行完毕、上下文被销毁后,异步操作才抛出未被捕获的异常。
  • 在复杂场景中,文件系统操作的天然延迟会触发同样的问题:测试 runner 认为测试已结束,但后续异步逻辑仍在运行,抛出的异常无法被测试的try/catch捕获,最终触发全局未捕获错误。

解决方案

1. 明确控制异步操作生命周期

调整逻辑,确保测试函数退出前所有异步操作完成,且仅在文件确实存在时执行删除操作:

const file = "exp.txt";

Deno.test("handle rejected promise", async (t) => {
  let fileExists = false;
  try {
    await Deno.stat(file);
    fileExists = true;
  } catch (e) {
    if (e instanceof Deno.errors.NotFound) {
      await Deno.writeTextFile(file, "some text");
      fileExists = true;
    } else {
      throw e;
    }
  }
  
  // 仅确认文件存在时执行删除,避免NotFound错误
  if (fileExists) {
    await Deno.remove(file);
  }
});

2. 使用官方标准库的exists方法

直接采用Deno官方推荐的exists方法(内部已优化竞态处理),替代手动stat+错误捕获的逻辑:

import { exists } from "https://deno.land/std/fs/mod.ts";

const file = "exp.txt";

Deno.test("handle file operations", async (t) => {
  try {
    if (!(await exists(file))) {
      await Deno.writeTextFile(file, "some text");
    }
  } finally {
    // 删除前再次检查文件存在性
    if (await exists(file)) {
      await Deno.remove(file);
    }
  }
});

3. 用测试步骤锁定异步操作

使用t.step包裹异步逻辑,确保测试 runner 等待所有子步骤完成后再结束测试:

const file = "exp.txt";

Deno.test("handle rejected promise", async (t) => {
  await t.step("check and create file", async () => {
    try {
      await Deno.stat(file);
    } catch (e) {
      if (e instanceof Deno.errors.NotFound) {
        await Deno.writeTextFile(file, "some text");
      } else {
        throw e;
      }
    }
  });

  await t.step("clean up file", async () => {
    // 捕获删除时的NotFound错误,避免全局异常
    try {
      await Deno.remove(file);
    } catch (e) {
      if (!(e instanceof Deno.errors.NotFound)) {
        throw e;
      }
    }
  });
});

4. 升级Deno版本

该问题在Deno 1.28.x版本出现,后续稳定版可能已修复测试 runner 的时序问题,建议升级至最新官方稳定版本。

内容的提问来源于stack exchange,提问作者Frank de Groot - Schouten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 02:35:19