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?
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é)
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';
}
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;
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
Mais cela ne fonctionne pas si vous liez un générateur, par exemple:
foo = test.bind(bar);
console.log(foo instanceof GeneratorFunction); // false
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
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
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.
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());
}
}
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)) {
...
}
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