JS:如何在回调中使用生成器和产量

JS: how to use generator and yield in a callback

提问人:Xaree Lee 提问时间:12/26/2016 更新时间:11/12/2023 访问量:13862

问:

我使用JS生成器在回调中生成一个值:setTimeout

function* sleep() {
  // Using yield here is OK
  // yield 5; 
  setTimeout(function() {
    // Using yield here will throw error
    yield 5;
  }, 5000);
}

// sync
const sleepTime = sleep().next()

为什么我不能在生成器的回调中生成值?

JavaScript 回调 yield

评论


答:

18赞 guest271314 12/26/2016 #1

function* 声明是同步的。您可以生成一个新对象,链接到以检索解析的值Promise.then().next().valuePromise

function* sleep() {
  yield new Promise(resolve => {
    setTimeout(() => {
      resolve(5);
    }, 5000);
  })
}

// sync
const sleepTime = sleep().next().value
  .then(n => console.log(n))
  .catch(e => console.error(e));

评论

27赞 Dan Dascalescu 6/14/2019
这只会解决一次,因此不会产生太大:)例如,代码如何从回调中重复?yieldsetInterval
0赞 ahgood 8/26/2019
.then(n => console(n))应该是.then(n => console.log(n))
6赞 Lloyd 7/5/2021
只产生一次的发电机有什么意义?
0赞 Fuczi Fuczi 7/15/2021
这对我有用。我需要一个生成器来为执行 ADB exec-out 的生成过程产生一次。在进程完成执行后,我从管道中捕获了 stdout。然后,我用字符串生成 resolve promise。对于这种类型的使用:)来说,它就像一个魅力
0赞 Benjamin 7/22/2021
>只产生一次的发电机有什么意义呢?- >浏览器兼容性
7赞 Jacob 8/8/2022 #2

我来到这个问题是为了寻找一种方法来将每隔一段时间调用的回调(例如节点流、事件侦听器或回调)转换为异步可迭代,经过一些研究,我找到了这个 NPM 包可以完成所有这些工作:EventIteratorsetInterval

EventIterator是一个小型模块,可大大简化将事件发射器、事件目标和类似对象转换为 EcmaScript 异步迭代器的过程。它适用于浏览器和 Node.js 环境。

一个基本的可迭代对象:setInterval

import { EventIterator } from "event-iterator"
const counter = ms =>
  new EventIterator(({ push }) => {
    let count = 0
    const interval = setInterval(() => push(++count), ms)
    return () => clearInterval(interval)
  })

for await (const count of counter(1000)) console.log(count)

(想想喜欢)。pushyield

虽然这在技术上并不能回答这个问题,但公认的答案也没有真正回答它,而且这个解决方案似乎非常接近 OP 所寻找的。

评论

2赞 Boiethios 12/3/2022
这应该是公认的答案。另一个是完全没用的,因为它只能产生一次。OTOH,我可以成功地使用它来轻松创建迭代器。EventIterator
0赞 Pandurang Parwatikar 1/5/2023 #3

扩展 guest271314 的答案。

下面的代码在循环中产生。因此可以产生不止一次。

async function* foo(loopVal) {
  for(let i=0;i<loopVal;i++){
    yield new Promise(resolve => {
      setTimeout(() => {
        resolve(i);
      }, 5000);
    })
  }
}

(async function() {
  for await (const num of foo(5)) {
    console.log(num);
  }
})();

评论

1赞 Jacob 2/21/2023
这个答案是不正确的,因为它只会立即产生五次超时
0赞 Zero Trick Pony 3/6/2023 #4

要直接回答问题,不能使用“*”/“yield”语法来做到这一点。从这里: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield

...“yield”关键字只能在包含它的生成器函数中直接使用。“它不能在嵌套函数中使用”,例如回调。

对于OP的“为什么”问题,无益的答案是,它被ECMAScript语言规范所禁止,至少在严格模式下是这样:https://262.ecma-international.org/9.0/#sec-generator-abstract-operations

关于原因的更多直觉:生成器的实现是“yield”关键字暂停其生成器函数的执行。生成器函数的执行在其他方面是普通的,当它返回时,它生成的可迭代对象结束。这向调用方发出信号,表明不再有值传入,任何等待它的循环也将结束。在那之后,即使嵌套回调再次运行,也没有机会向任何感兴趣的调用方提供任何内容。

尽管回调或其他嵌套函数可以绑定来自外部生成器的变量,但它可以逃避生成器的生存期,并在任何其他时间/地点/上下文中运行。这意味着所需的 yield 关键字可能没有要暂停的功能,也没有要为其生成值的调用者或循环。我推测,严格模式语法错误被放在这里是为了防止代码作者发生一些悄无声息的不受欢迎的事情。

也就是说,生成器语法对于创建 OP 的预期效果不是必需的。当目标只是让 “next()” 工作,或参与异步迭代器协议(“for await (...)”)时,这些协议可以符合使用普通函数和对象,而不需要屈服。您要遵守的协议记录在下面:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

...另一个提到 EventIterator 的答案是帮助程序代码的一个示例,它使这更容易实现。