【问题标题】:ECMA6 - Use of Generator Function in JavaScriptECMA6 - 在 JavaScript 中使用生成器函数
【发布时间】:2016-04-08 20:46:59
【问题描述】:

这几天我开始了解 ES6 版本中引入的 JavaScript 中的 Generator 函数。

有很多地方解释了生成器,但对我来说似乎太有趣的是,到处都在说

生成器函数是一种同步编写异步代码的方法。

我想提出的问题是“为什么有必要仅仅为了它而引入一种完全不同的编程策略?”

我了解 JS 代码的异步特性使新手难以理解和调试代码,但是否需要完全改变编码风格?

我可能错了,或者没有完全理解它的介绍背后的概念,但对这一切的好奇是促使我提出这个问题的原因。

【问题讨论】:

  • 它们不仅仅是编写异步代码:生成器提供了一个可以永远运行的函数,有时会暂停,并允许在迭代中交换值。
  • 哦,我同意这一点,但你能给我一个我需要那种功能的用例吗?
  • 您可以使用它们来生成 GUID、访问数据存储等。我什至在一个之上构建了一个基于文本的冒险游戏,发送用户命令,并接收每个收益的玩家状态。这种使用主要是出于好奇:)
  • 如果您将其视为现在可以使用生成器制作的“假装自上而下”代码,它并不完全是“新手友好”,所以我认为这不是实现工匠的动机。稍后当我们开始对巨大/无限/慢速对象使用自定义迭代时,真正的力量可能会很明显。
  • 单独的生成器不允许您“同步”编写异步代码(顺便说一句,这听起来很误导)。 Generators + Promises 允许你这样做。但这仍然很奇怪,因此我们将来会得到async/await。我认为 async/await 只不过是生成器 + 承诺很漂亮,但我一直认为这是生成器的(可能是精心策划的)副作用。

标签: javascript ecmascript-6


【解决方案1】:

因为闭包对于简单的迭代不太方便;为相当常见的任务简化语法是值得的,即使该语言以前支持相同的模式。比较一下:

function chain() {
    var args = Array.from(arguments);
    return function() {
        if (args.length === 0) return undefined; // Or some other sentinel
        var nextval = args[0].shift(); // Destructive to avoid copies or more closure vars
        if (args[0].length === 0) args.shift();
        return nextval;
    };
}
var x;
// a, b and c must be indexable, e.g. Arrays; we can't handle other closures without
// requiring some API specific protocol for generation
for (var nextchain = chain(a, b, c); (x = nextchain()) !== undefined;) {
    // do stuff with current value
}

到:

function* chain() {
    for (var i = 0; i < arguments.length; ++i)
        yield* arguments[i];
}
// a, b and c can be any iterable object; yield* can handle
// strings, Arrays, other generators, etc., all with no special handling
for (var x of chain(a, b, c)) {
    // do stuff with current value
}

当然,节省的代码行数并不令人难以置信。它主要只是减少样板文件和不必要的名称,消除对简单情况处理闭包的需要,并使用for...of 语法,提供一种通用机制来迭代任意可迭代事物,而不是要求用户显式构造初始闭包和按名称推进它。但如果这种模式足够普遍,那就足够有用了。

如 cmets 中所述,a, b, c 必须类似于 Array,用于基于闭包的方法(或者您将使用不同的基于闭包的方法,chain 的作者对传递给它的内容施加任意要求,对于Array-like 的东西与类似生成器的闭包的特殊情况)并且处理是破坏性的(您需要添加更多的闭包状态或制作副本以使其成为非破坏性的,使其更复杂或更慢);对于yield* 的基于生成器的方法,不需要特殊情况。这使得生成器无需复杂的规格即可组合;它们可以轻松地相互构建。

【讨论】:

    【解决方案2】:

    场景 1(异步):您可​​能听说过编写“非阻塞”javascript 的重要性。当我们进行 I/O 操作时,我们使用 javascript 中的回调或 Promise 来编写非阻塞 javascript 代码。

    场景 2(同步):在 javascript 中运行无限循环,例如:node -e 'while(true) {}' 可能会冻结您的计算机

    考虑到所有这些,ES6 生成器允许我们在函数中间有效地“暂停”执行并在未来某个时间恢复它(异步代码同步)

    用例:假设您需要对一系列无限值进行操作。在这种情况下,数组不会有帮助,我们可以使用 ES6 生成器函数

    var iterator = generateRandoms();   //suppose it is a generator function which loops through infinite sequence
    
    //generator functions returns a next function which can be called anytime in your code to get the next value from the sequence
    
    console.log(iterator.next());     // { value: 0.4900301224552095, done: false }
    console.log(iterator.next());     // { value: 0.8244022422935814, done: false }
    

    就复杂性而言,它是一种新语法,但很快就能掌握。

    进一步阅读:

    1. http://x-team.com/2015/04/generators-work/
    2. https://msdn.microsoft.com/en-in/library/dn858237(v=vs.94).aspx

    【讨论】:

      【解决方案3】:

      其实generator函数与其说是异步,不如说是一个可以被中断的函数,如何中断流程是由generator的调用者通过iterator来决定的——下面有更多解释

      function* ab3() {
        console.log(1);
        var c = yield 2;
        console.log(2);
        return 1 + c;
        console.log(3);
      }
      
      1. 生成器函数是可以在两者之间截断的函数

      2. iterator.next() 调用时开始执行,调用时生成器函数返回迭代器

      3. 第一次调用 next 将运行生成器函数的语句直到第一次 yield 并返回生成的值

      4. iterator.next 的第二次调用将运行到下一个 yield 或 return 语句 因此它将接收通过 next(3) // (本例中为 3)传递的数据并将其存储在变量(分配了产量的变量)中,var y = yield 2;所以 3 将存储在 y 中;和语句执行直到返回语句,它会在这里返回 {done:true} 因为我们已经到了生成器函数的末尾

      5. iterator.next() 的下一次执行(这里是第 3 次)将返回 {value: undefined, done:true},因为生成器函数没有进一步返回任何内容

      【讨论】:

        猜你喜欢
        • 2021-10-28
        • 1970-01-01
        • 1970-01-01
        • 2018-01-30
        • 2016-11-09
        • 1970-01-01
        • 2016-08-02
        • 1970-01-01
        相关资源
        最近更新 更多