如何让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()会标记生成器为终止状态,但要等到当前的异步操作完成、生成器回到可执行状态后,才会处理这个终止信号。 - 时间线拆解:
- 0ms:生成器产出
0,进入await delay(100) - 100ms:等待完成,回到循环,产出
1,再次进入await delay(100) - 200ms:等待完成,产出
2,进入await delay(100) - 250ms:调用
values.return(),生成器标记为终止,但此时它还在等待delay(100)结束(要到300ms) - 300ms:等待完成,生成器回到循环,此时才会检测到终止信号,但在这之前,
yield n++已经执行,产出了3,之后才会处理return,终止生成器。
- 0ms:生成器产出
如何在保留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
相关产品推荐
相关产品推荐

