web-dev-qa-db-ja.com

関数がジェネレータかどうかを確認する

Nodejs v0.11.2でジェネレーターを操作しましたが、関数の引数がジェネレーター関数であることを確認する方法を知りたいと思います。

私はこの方法typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)を見つけましたが、これが良い(そして将来的に機能する)方法であるかどうかはわかりません。

この問題についてどう思いますか?

55
Dima Vidmich

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

これら2つは同じように動作します(.throw()をマイナスしますが、追加することもできます)

37
Erik Arvidsson

Nodejsの最新バージョン(v0.11.12で確認済み)では、コンストラクター名がGeneratorFunctionと等しいかどうかを確認できます。これがどのバージョンで登場したのかはわかりませんが、動作します。

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

これはノードと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
9
Nick Sotiros

私はこれを使っています:

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

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

6
freddyrangel

ノード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
4
Lycan

@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
}

それだけです。

2
kyr0

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());
    }
}
2
Lex

私は koa がどのように実行するかを確認し、彼らはこのライブラリを使用します: https://github.com/ljharb/is-generator-function

こんな風に使えます

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

ここでまだ対処されていない問題は、ジェネレータ関数で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
0
Lorenzo

古い学校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));

}
0
Mario Škrlec