Nodejs v0.11.2でジェネレーターを操作しましたが、関数の引数がジェネレーター関数であることを確認する方法を知りたいと思います。
私はこの方法typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)
を見つけましたが、これが良い(そして将来的に機能する)方法であるかどうかはわかりません。
この問題についてどう思いますか?
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()をマイナスしますが、追加することもできます)
Nodejsの最新バージョン(v0.11.12で確認済み)では、コンストラクター名がGeneratorFunction
と等しいかどうかを確認できます。これがどのバージョンで登場したのかはわかりませんが、動作します。
function isGenerator(fn) {
return fn.constructor.name === 'GeneratorFunction';
}
これはノードとFirefoxで機能します:
var GeneratorFunction = (function*(){yield undefined;}).constructor;
function* test() {
yield 1;
yield 2;
}
console.log(test instanceof GeneratorFunction); // true
ただし、ジェネレータをバインドする場合は機能しません。たとえば、次のようになります。
foo = test.bind(bar);
console.log(foo instanceof GeneratorFunction); // false
私はこれを使っています:
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;
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
ノード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
@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
}
それだけです。
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());
}
}
私は koa がどのように実行するかを確認し、彼らはこのライブラリを使用します: https://github.com/ljharb/is-generator-function 。
こんな風に使えます
const isGeneratorFunction = require('is-generator-function');
if(isGeneratorFunction(f)) {
...
}
ここでまだ対処されていない問題は、ジェネレータ関数で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
古い学校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));
}