web-dev-qa-db-fra.com

vérifier si la fonction est un générateur

J'ai joué avec des générateurs dans Nodejs v0.11.2 et je me demande. Comment puis-je vérifier que l'argument de ma fonction est générateur?.

J'ai trouvé ceci typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function) mais je ne suis pas sûr que ce soit bon (et fonctionne à l'avenir).

Quelle est votre opinion sur ce problème?

45
Dima Vidmich

Nous en avons parlé lors des réunions en face à face du TC39 et il est délibéré que nous n'exposons pas de moyen de détecter si une fonction est un générateur ou non. La raison en est que n'importe quelle fonction peut renvoyer un objet itérable, donc peu importe qu'il s'agisse d'une fonction ou d'une fonction génératrice.

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++;
  }
}

Ces deux se comportent de manière identique (moins .throw () mais cela peut aussi être ajouté)

32
Erik Arvidsson

Dans la dernière version de nodejs (j'ai vérifié avec v0.11.12), vous pouvez vérifier si le nom du constructeur est égal à GeneratorFunction. Je ne sais pas quelle version cela est sorti mais ça marche.

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

J'utilise ceci:

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;
9
Albert

cela fonctionne dans les noeuds et dans firefox:

var GeneratorFunction = (function*(){yield undefined;}).constructor;

function* test() {
   yield 1;
   yield 2;
}

console.log(test instanceof GeneratorFunction); // true

jsfiddle

Mais cela ne fonctionne pas si vous liez un générateur, par exemple:

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

La bibliothèque co de TJ Holowaychuk possède la meilleure fonction pour vérifier si quelque chose est une fonction génératrice. Voici le code source:

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

Référence: https://github.com/tj/co/blob/717b043371ba057cb7a4a4e47120d598116ed7/index.js#L221

6
freddyrangel

Dans le noeud 7, vous pouvez instanceof contre les constructeurs pour détecter à la fois les fonctions du générateur et les fonctions asynchrones:

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

Cela fonctionne pour toutes les circonstances dans mes tests. Un commentaire ci-dessus indique que cela ne fonctionne pas pour les expressions de fonction de générateur nommé, mais je ne parviens pas à reproduire:

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

Le seul problème est que la propriété .constructor des instances peut être modifiée. Si quelqu'un était vraiment déterminé à vous causer des problèmes, il pourrait le résoudre:

// 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
4
Lycan

Comme @Erik Arvidsson l'a déclaré, il n'y a pas de moyen standard de vérifier si une fonction est une fonction génératrice. Mais vous pouvez, bien sûr, vérifier l'interface, une fonction du générateur remplit les fonctions suivantes:

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
}

C'est ça.

2
kyr0

La documentation JavaScript de Mozilla décrit la méthode Function.prototype.isGenerator- API MDN . Nodejs ne semble pas l'implémenter. Toutefois, si vous souhaitez limiter votre code à la définition de générateurs avec function* uniquement (aucun objet itérable renvoyé), vous pouvez l'augmenter en l'ajoutant vous-même à un contrôle de compatibilité en aval:

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

J'ai vérifié comment koa fonctionne et ils utilisent cette bibliothèque: https://github.com/ljharb/is-generator-function .

Vous pouvez l'utiliser comme ça

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

Une difficulté qui n’est pas encore abordée ici est que si vous utilisez la méthode bind sur la fonction du générateur, le nom de son prototype de «GeneratorFunction» devient «Fonction».

Il n'y a pas de méthode Reflect.bind neutre, mais vous pouvez contourner ce problème en réinitialisant le prototype de l'opération liée à celui de l'opération d'origine.

Par exemple:

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