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

单元测试中使用asyncIterator始终无法解析值的问题排查

问题分析与修复

你的测试代码存在几个关键问题,导致迭代无法正常推进并完成:

核心问题

  1. 每次read都创建新的异步迭代器
    在getReader的read方法里,每次调用都会执行mockReadable[Symbol.asyncIterator]()生成新的迭代器实例,这意味着每次读取都会从头开始,永远拿到第一个yield的值,无法推进到后续内容。

  2. 无限循环未处理结束状态
    你的while(true)循环没有在done为true时退出,导致测试会无限执行,最终Jest会超时失败。

  3. 异步迭代器错误使用yield null
    标准的异步迭代器不需要通过yield null来标记结束,当迭代器执行完毕时,next()会自动返回{ done: true, value: undefined }。

  4. 外部计数变量与迭代器状态脱节
    使用外部变量i来控制结束状态是不可靠的,应该直接依赖迭代器自身的done状态。

修复后的代码

var util = require("util");

describe("stream", () => {
  it("should work", async () => {
    const completionStream = {
      toReadableStream: () => {
        // 预先创建异步迭代器实例,避免每次read都新建
        const iterator = (async function* () {
          const encoder = new util.TextEncoder("utf-8");
          yield encoder.encode(
            JSON.stringify({
              id: "1",
              object: "text",
              created: 1629159835,
              model: "gpt4",
              choices: [
                {
                  index: 0,
                  delta: { content: "Hello" },
                  finish_reason: null,
                },
              ],
            })
          );
          yield encoder.encode(
            JSON.stringify({
              id: "1",
              object: "text",
              created: 1629159835,
              model: "gpt4",
              choices: [
                {
                  index: 0,
                  delta: { content: "there" },
                  finish_reason: null,
                },
              ],
            })
          );
          // 不需要yield null,迭代器自然结束即可
        })();

        const mockReadable = {
          [Symbol.asyncIterator]: () => iterator,
          getReader: () => {
            return {
              read: async () => {
                // 复用同一个迭代器实例
                const result = await iterator.next();
                if (result.done) {
                  return { done: true, value: null };
                }
                return { done: false, value: result.value };
              },
            };
          },
        };
        return mockReadable;
      },
    };

    const reader = completionStream.toReadableStream().getReader();
    let receivedContents = [];

    while (true) {
      const { done, value } = await reader.read();
      if (done) break; // 处理结束状态,退出循环

      // 解码数据并断言内容
      const chunk = new util.TextDecoder("utf-8").decode(value);
      const data = JSON.parse(chunk);
      receivedContents.push(data.choices[0].delta.content);
      expect(done).toBe(false);
    }

    // 最终断言完整内容
    expect(receivedContents.join("")).toBe("Hellothere");
  });
});

关键优化点

  • 预先创建异步迭代器实例,让所有read调用复用同一个迭代器,确保能正常推进。
  • 移除多余的yield null,依赖迭代器自然结束的状态。
  • 在循环中检测done状态并退出,避免无限循环。
  • 添加了解码和最终内容的断言,让测试逻辑更完整。

内容的提问来源于stack exchange,提问作者user686483

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 18:54:51