【问题标题】:check if function is a generator检查函数是否是生成器
【发布时间】:2013-05-21 05:38:03
【问题描述】:

我在 Nodejs v0.11.2 中使用过生成器,我想知道 如何检查我的函数的参数是生成器函数。

我找到了这种方式typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function),但我不确定这是否是好的(并且在未来工作)方式。

你对这个问题有什么看法?

【问题讨论】:

  • 非常确定 f instanceof GeneratorFunction 应该可以工作,基于当前 ES6 草案的 15.19.3.1 The GeneratorFunction Constructor
  • nodejs v0.11.2 没有 GeneratorFunction 所以我认为 v8 v3.19.0 也没有它。但是是的,这个检查会简单得多。
  • 该构造函数似乎是最新草案的新增内容。搜索上一个,我没有找到那个文本。假设它保留在规范中,我想它会在某个时候出现。 编辑: ...啊,是的,我在更改说明中看到它“为生成器函数和生成器方法定义添加语义” ...所以看起来它刚刚登陆10 天前。
  • 我看到由于一些测试问题github.com/v8/v8/commit/…,此更改已从 v8 中删除
  • 一定有什么!!!生成器不同于函数..

标签: javascript generator yield ecmascript-6


【解决方案1】:
function isGenerator(target) {
  return target[Symbol.toStringTag] === 'GeneratorFunction';
}

function isGenerator(target) {
  return Object.prototype.toString.call(target) === '[object GeneratorFunction]';
}

【讨论】:

    【解决方案2】:

    老派Object.prototype.toString.call(val) 似乎也有效。在 Node 版本 11.12.0 中,它返回 [object Generator],但最新的 Chrome 和 Firefox 返回 [object GeneratorFunction]

    所以可能是这样的:

    function isGenerator(val) {
        return /\[object Generator|GeneratorFunction\]/.test(Object.prototype.toString.call(val));
    
    }
    

    【讨论】:

      【解决方案3】:

      这在 node 和 firefox 中有效:

      var GeneratorFunction = (function*(){yield undefined;}).constructor;
      
      function* test() {
         yield 1;
         yield 2;
      }
      
      console.log(test instanceof GeneratorFunction); // true
      

      jsfiddle

      但是如果你绑定一个生成器就不行了,例如:

      foo = test.bind(bar); 
      console.log(foo instanceof GeneratorFunction); // false
      

      【讨论】:

      • 对我来说,在 Chromium 76 和节点 10 中,绑定生成器也可以工作。
      【解决方案4】:

      我检查了koa 是如何做到的,他们使用这个库:https://github.com/ljharb/is-generator-function

      你可以这样使用它

      const isGeneratorFunction = require('is-generator-function');
      if(isGeneratorFunction(f)) {
          ...
      }
      

      【讨论】:

      • 我将添加一行代码来演示该库的有用性,但我仍然认为在这里提到一个解决所述问题的可重用库是有道理的。
      【解决方案5】:

      正如@Erik Arvidsson 所说,没有标准方法来检查函数是否是生成器函数。但是您可以肯定的是,只需检查接口,生成器函数就可以实现:

      function* fibonacci(prevPrev, prev) {
      
        while (true) {
      
          let next = prevPrev + prev;
      
          yield next;
      
          prevPrev = prev;
          prev = next;
        }
      }
      
      // fetch get an instance
      let fibonacciGenerator = fibonacci(2, 3)
      
      // check the interface
      if (typeof fibonacciGenerator[Symbol.iterator] == 'function' && 
          typeof fibonacciGenerator['next'] == 'function' &&
          typeof fibonacciGenerator['throw'] == 'function') {
      
        // it's safe to assume the function is a generator function or a shim that behaves like a generator function
      
        let nextValue = fibonacciGenerator.next().value; // 5
      }
      

      就是这样。

      【讨论】:

      • 我会检查fn.constructor.name,但由于该函数是通过Proxy 传递的,它报告它是一个常规函数......所以我必须按照你的建议做,然后应用一个协程
      • 如果 Symbol.iterator 像鸭子,next 像鸭子,throw 像鸭子,那么......
      【解决方案6】:

      这里尚未解决的一个困难是,如果您在生成器函数上使用bind 方法,它会将其原型的名称从“GeneratorFunction”更改为“Function”。

      没有中立的Reflect.bind 方法,但是您可以通过将绑定操作的原型重置为原始操作的原型来解决此问题。

      例如:

      const boundOperation = operation.bind(someContext, ...args)
      console.log(boundOperation.constructor.name)       // Function
      Reflect.setPrototypeOf(boundOperation, operation)
      console.log(boundOperation.constructor.name)       // GeneratorFunction
      

      【讨论】:

        【解决方案7】:

        在节点 7 中,您可以 instanceof 针对构造函数来检测生成器函数和异步函数:

        const GeneratorFunction = function*(){}.constructor;
        const AsyncFunction = async function(){}.constructor;
        
        function norm(){}
        function*gen(){}
        async function as(){}
        
        norm instanceof Function;              // true
        norm instanceof GeneratorFunction;     // false
        norm instanceof AsyncFunction;         // false
        
        gen instanceof Function;               // true
        gen instanceof GeneratorFunction;      // true
        gen instanceof AsyncFunction;          // false
        
        as instanceof Function;                // true
        as instanceof GeneratorFunction;       // false
        as instanceof AsyncFunction;           // true
        

        这适用于我的测试中的所有情况。上面的评论说它不适用于命名生成器函数表达式,但我无法重现:

        const genExprName=function*name(){};
        genExprName instanceof GeneratorFunction;            // true
        (function*name2(){}) instanceof GeneratorFunction;   // true
        

        唯一的问题是实例的.constructor 属性可以更改。如果有人真的决心给你带来麻烦,他们可能会破坏它:

        // Bad people doing bad things
        const genProto = function*(){}.constructor.prototype;
        Object.defineProperty(genProto,'constructor',{value:Boolean});
        
        // .. sometime later, we have no access to GeneratorFunction
        const GeneratorFunction = function*(){}.constructor;
        GeneratorFunction;                     // [Function: Boolean]
        function*gen(){}
        gen instanceof GeneratorFunction;      // false
        

        【讨论】:

        • 为我工作。伟大的思想!当然,尼克·索蒂罗斯 (Nick Sotiros) 的答案总是比你早 2 年。
        • 如果您使用异步生成器函数,此答案将失败,仅返回 1 个 true:async function*asgen(){}
        【解决方案8】:

        我们在 TC39 面对面会议中讨论过这个问题,我们有意不公开检测函数是否为生成器的方法。原因是任何函数都可以返回一个可迭代对象,所以不管它是函数还是生成器函数。

        var iterator = Symbol.iterator;
        
        function notAGenerator() {
          var  count = 0;
          return {
            [iterator]: function() {
              return this;
            },
            next: function() {
              return {value: count++, done: false};
            }
          }
        }
        
        function* aGenerator() {
          var count = 0;
          while (true) {
            yield count++;
          }
        }
        

        这两个行为相同(减去 .throw() 但也可以添加)

        【讨论】:

        • 哇...太糟糕了 :( 无法确定是生成器函数还是简单函数将不允许出现好事情,例如与 primise 库(如 Q.async)集成以自动检测生成器并获取/push 值有基于生成器的漂亮和干净的“primise” api。
        • @Erik Arvidsson 我们在哪里可以找到 Symbol 函数的文档?
        • 我必须注意,即使使用最新的 Node.js 开发版本,这个 sn-p 也不起作用,我在 [iterator]: function() { 收到了一个 Unexpected token [。这是哪里来的?
        • @LexPodgorny 我发现很少提及 Symbol、hereherehere
        • @Erik,所以你是说生成器函数只是一类特殊的函数,没有什么不同?那么也许我们可以通过检查一个函数是否具有生成器的所有特性来判断它是否是一个生成器(返回包含next[iterator]的对象,next返回valuecount等)这在可预见的未来始终有效?
        【解决方案9】:

        TJ Holowaychuk 的co 库具有检查某事物是否为生成器函数的最佳功能。以下是源代码:

        function isGeneratorFunction(obj) {
           var constructor = obj.constructor;
           if (!constructor) return false;
           if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
           return isGenerator(constructor.prototype);
        }
        

        参考:https://github.com/tj/co/blob/717b043371ba057cb7a4a2a4e47120d598116ed7/index.js#L221

        【讨论】:

          【解决方案10】:

          在最新版本的nodejs(我用v0.11.12验证)你可以检查构造函数名称是否等于GeneratorFunction。我不知道这是什么版本,但它可以工作。

          function isGenerator(fn) {
              return fn.constructor.name === 'GeneratorFunction';
          }
          

          【讨论】:

          【解决方案11】:

          我正在使用这个:

          var sampleGenerator = function*() {};
          
          function isGenerator(arg) {
              return arg.constructor === sampleGenerator.constructor;
          }
          exports.isGenerator = isGenerator;
          
          function isGeneratorIterator(arg) {
              return arg.constructor === sampleGenerator.prototype.constructor;
          }
          exports.isGeneratorIterator = isGeneratorIterator;
          

          【讨论】:

          • 我将其缩短为 Generator = (function*(){}).constructor; g instanceof Generator,不幸的是 (function*(){}).prototype.constructor 不是用于检查生成器迭代器的 instanceof 的有效参数
          【解决方案12】:

          Mozilla javascript 文档描述了Function.prototype.isGenerator 方法MDN API。 Nodejs 似乎没有实现它。但是,如果您愿意将代码限制为仅使用 function* 定义生成器(不返回可迭代对象),您可以通过前向兼容性检查自己添加它来增加它:

          if (typeof Function.prototype.isGenerator == 'undefined') {
              Function.prototype.isGenerator = function() {
                  return /^function\s*\*/.test(this.toString());
              }
          }
          

          【讨论】:

          • 您可能需要考虑可能存在的空白。 function *(args) {}function* (args){} 我都见过。如果 node 本身添加了一个检测器,我不会感到惊讶,因为 toString 太贵了
          猜你喜欢
          • 2016-06-30
          • 2014-04-17
          • 2013-07-15
          • 2016-03-10
          • 2020-05-25
          • 2019-05-23
          • 2017-07-31
          • 2018-11-08
          • 2011-07-26
          相关资源
          最近更新 更多