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

如何让AsyncGenerator调用return()后不再产出后续值?

问题

MDN中提到:

AsyncGenerator的return()方法相当于在生成器主体当前暂停位置插入一条return语句,这会终止生成器,结合try...finally块还可让生成器执行清理任务。

但为什么以下代码会打印0至3而非仅0至2?

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const values = (async function* delayedIntegers() {
  let n = 0;
  while (true) {
    yield n++;
    await delay(100);
  }
})();

await Promise.all([
  (async () => {
    for await (const value of values) console.log(value);
  })(),
  (async () => {
    await delay(250);
    values.return();
  })(),
]);

我添加日志后发现,调用return()时,AsyncGenerator实例并未处于暂停状态(主体执行不在yield语句处),要等执行到yield时才会处理return操作,而不是直接返回,而是先产出下一个值才暂停并执行return。

有没有办法检测return()已被调用,从而不再产出后续值?


我可以自行实现AsyncIterator接口,但这样会失去async generator支持的yield语法:

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const values = (() => {
  let n = 0;
  let done = false;
  return {
    [Symbol.asyncIterator]() {
      return this;
    },
    async next() {
      if (done) return { done, value: undefined };
      if (n !== 0) {
        await delay(100);
        if (done) return { done, value: undefined };
      }
      return { done, value: n++ };
    },
    async return() {
      done = true;
      return { done, value: undefined };
    },
  };
})();

await Promise.all([
  (async () => {
    for await (const value of values) console.log(value);
  })(),
  (async () => {
    await delay(250);
    values.return();
  })(),
]);
解答

为什么会打印0-3?

你观察到的现象本质是异步生成器的执行时机问题:

  • 当调用values.return()时,生成器正处于await delay(100)的等待阶段,并非暂停在yield语句上。此时return()会标记生成器为终止状态,但要等到当前的异步操作完成、生成器回到可执行状态后,才会处理这个终止信号。
  • 时间线拆解:
    1. 0ms:生成器产出0,进入await delay(100)
    2. 100ms:等待完成,回到循环,产出1,再次进入await delay(100)
    3. 200ms:等待完成,产出2,进入await delay(100)
    4. 250ms:调用values.return(),生成器标记为终止,但此时它还在等待delay(100)结束(要到300ms)
    5. 300ms:等待完成,生成器回到循环,此时才会检测到终止信号,但在这之前,yield n++已经执行,产出了3,之后才会处理return,终止生成器。

如何在保留yield语法的前提下检测return调用?

可以借助yield的返回值或者try...finally块来实现,两种方案都能保留yield语法:

方案1:利用yield的返回值识别终止信号

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const values = (async function* delayedIntegers() {
  let n = 0;
  while (true) {
    // 调用return()时,yield会收到传入的终止标记
    const terminateSignal = yield n++;
    // 检测到终止信号则退出循环
    if (terminateSignal !== undefined) break;
    await delay(100);
  }
})();

await Promise.all([
  (async () => {
    for await (const value of values) console.log(value);
  })(),
  (async () => {
    await delay(250);
    // 调用return时传入标记值
    await values.return('stop');
  })(),
]);

方案2:用try...finally设置终止标志

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const values = (async function* delayedIntegers() {
  let n = 0;
  let isTerminated = false;
  try {
    while (true) {
      if (isTerminated) break;
      yield n++;
      await delay(100);
    }
  } finally {
    // 调用return()时会进入finally块,标记终止状态
    isTerminated = true;
  }
})();

await Promise.all([
  (async () => {
    for await (const value of values) console.log(value);
  })(),
  (async () => {
    await delay(250);
    await values.return();
  })(),
]);

这两种方案都能在调用return()后,让生成器立即停止产出后续值,同时保留async generator的yield语法优势。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:50:29