我可以阻止“AsyncGenerator”在调用“return()”方法后产生结果吗?

mfu*_*n26 14 javascript generator async-await

AsyncGenerator.prototype.return() - JavaScript | AsyncGenerator.prototype.return() - JavaScript | AsyncGenerator.prototype.return() MDN指出:

\n
\n

异步生成器的方法return()就像return在当前挂起位置将一条语句插入到生成器主体中一样,该语句完成生成器并允许生成器在与块组合时执行任何清理任务try...finally

\n
\n

那么为什么以下代码打印0\xe2\x80\x933而不是仅打印0\xe2\x80\x93 2

\n
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst values = (async function* delayedIntegers() {\n  let n = 0;\n  while (true) {\n    yield n++;\n    await delay(100);\n  }\n})();\n\nawait Promise.all([\n  (async () => {\n    for await (const value of values) console.log(value);\n  })(),\n  (async () => {\n    await delay(250);\n    values.return();\n  })(),\n]);\n
Run Code Online (Sandbox Code Playgroud)\n

我尝试添加日志语句以更好地理解“当前挂起位置”在哪里,并且从我调用该方法时可以看出return()实例AsyncGenerator没有挂起(主体执行不在语句中yield)而不是返回一旦到达yield语句,下一个值就会yielded被挂起,此时“返回”最终发生。

\n

有什么方法可以检测该return()方法是否已被调用而不是yield之后调用?

\n
\n

我可以自己实现该AsyncIterator接口,但随后我失去了yield异步生成器支持的语法:

\n
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\nconst values = (() => {\n  let n = 0;\n  let done = false;\n  return {\n    [Symbol.asyncIterator]() {\n      return this;\n    },\n    async next() {\n      if (done) return { done, value: undefined };\n      if (n !== 0) {\n        await delay(100);\n        if (done) return { done, value: undefined };\n      }\n      return { done, value: n++ };\n    },\n    async return() {\n      done = true;\n      return { done, value: undefined };\n    },\n  };\n})();\n\nawait Promise.all([\n  (async () => {\n    for await (const value of values) console.log(value);\n  })(),\n  (async () => {\n    await delay(250);\n    values.return();\n  })(),\n]);\n
Run Code Online (Sandbox Code Playgroud)\n

Ber*_*rgi 9

\n

为什么代码会打印0\xe2\x80\x933而不是仅打印0\xe2\x80\x932?据我所知,当我调用该return()方法时,AsyncGenerator实例不会被挂起(主体执行不在语句中yield),并且不会在到达yield语句时返回,而是会编辑下一个值yield,然后在此时挂起“回归”终于发生了。

\n
\n

是的,正是如此。生成器已经在运行,因为for await \xe2\x80\xa6 of循环确实调用了它的.next()方法,因此生成器将在考虑.return()调用之前完成该操作。

\n

您在异步生成器上调用的所有方法都会排队。(在同步生成器中,您会收到“ TypeError:生成器已在运行”)。可以通过立即调用next多次来证明这一点:

\n

\r\n
\r\n
const values = (async function*() {\n  let i=0; while (true) {\n    await new Promise(r => { setTimeout(r, 1000); });\n    yield i++;\n  }\n})();\nvalues.next().then(console.log, console.error);\nvalues.next().then(console.log, console.error);\nvalues.next().then(console.log, console.error);\nvalues.return(\'done\').then(console.log, console.error);\nvalues.next().then(console.log, console.error);
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

\n

有什么方法可以检测该return()方法是否已被调用而不是yield之后调用?

\n
\n

不,不是来自发电机内部。实际上,如果您已经付出了努力来产生它,那么您可能仍然应该产生该价值。

\n

听起来您想要做的是当您希望发电机停止时忽略产生的值。您应该在for await \xe2\x80\xa6 of循环中执行此操作 - 您还可以使用它通过以下break语句来停止生成器:

\n

\r\n
\r\n
const delay = (ms) => new Promise((resolve) => {\n  setTimeout(resolve, ms);\n});\n\nasync function* delayedIntegers() {\n  let n = 0;\n  while (true) {\n    yield n++;\n    await delay(1000);\n  }\n}\n\n(async function main() {\n  const start = Date.now();\n  const values = delayedIntegers();\n  for await (const value of values) {\n    if (Date.now() - start > 2500) {\n      console.log(\'done:\', value);\n      break;\n    }\n    console.log(value);\n  }\n})();
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

但如果您确实想从外部中止生成器,则需要一个带外通道来发出取消信号。您可以AbortSignal为此使用:

\n

\r\n
\r\n
const delay = (ms, signal) => new Promise((resolve, reject) => {\n  function done() {\n    resolve();\n    signal?.removeEventListener("abort", stop);\n  }\n  function stop() {\n    reject(this.reason);\n    clearTimeout(handle);\n  }\n  signal?.throwIfAborted();\n  const handle = setTimeout(done, ms);\n  signal?.addEventListener("abort", stop, {once: true});\n});\n\nasync function* delayedIntegers(signal) {\n  let n = 0;\n  while (true) {\n    yield n++;\n    await delay(1000, signal);\n  }\n}\n\n(async function main() {\n  try {\n    const values = delayedIntegers(AbortSignal.timeout(2500));\n    for await (const value of values) {\n      console.log(value);\n    }\n  } catch(e) {\n    if (e.name != "TimeoutError") throw e;\n    console.log("done");\n  }\n})();
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

这实际上允许在超时期间停止生成器而不是在整秒过去之后。

\n