サーチ…
前書き
ジェネレータ関数( function*
キーワードで定義)はコルーチンとして実行され、イテレータを介して要求される一連の値を生成します。
構文
- 関数*名前(パラメータ){降伏値;戻り値}
- ジェネレータ=名前(引数)
- {値、完了} =ジェネレータ.next(値)
- {value、done} = generator.return(value)
- generator.throw(エラー)
備考
ジェネレータ機能は、ES 2015仕様の一部として導入された機能であり、一部のブラウザでは使用できません。また、 v6.0
、Node.jsで完全にサポートされています。詳細なブラウザ互換性リストについては、 MDNドキュメントを参照してください。ノードについては、 node.green Webサイトを参照してください。
ジェネレータ関数
ジェネレータ関数はfunction*
宣言で作成されます。呼び出されると、そのボディは即座に実行されません 。代わりに、 ジェネレータオブジェクトを返します。 ジェネレータオブジェクトは、関数の実行を「ステップスルー」するために使用できます。
関数本体内のyield
式は、実行を中断して再開できるポイントを定義します。
function* nums() {
console.log('starting'); // A
yield 1; // B
console.log('yielded 1'); // C
yield 2; // D
console.log('yielded 2'); // E
yield 3; // F
console.log('yielded 3'); // G
}
var generator = nums(); // Returns the iterator. No code in nums is executed
generator.next(); // Executes lines A,B returning { value: 1, done: false }
// console: "starting"
generator.next(); // Executes lines C,D returning { value: 2, done: false }
// console: "yielded 1"
generator.next(); // Executes lines E,F returning { value: 3, done: false }
// console: "yielded 2"
generator.next(); // Executes line G returning { value: undefined, done: true }
// console: "yielded 3"
早期反復出口
generator = nums();
generator.next(); // Executes lines A,B returning { value: 1, done: false }
generator.next(); // Executes lines C,D returning { value: 2, done: false }
generator.return(3); // no code is executed returns { value: 3, done: true }
// any further calls will return done = true
generator.next(); // no code executed returns { value: undefined, done: true }
ジェネレータ関数にエラーを投げる
function* nums() {
try {
yield 1; // A
yield 2; // B
yield 3; // C
} catch (e) {
console.log(e.message); // D
}
}
var generator = nums();
generator.next(); // Executes line A returning { value: 1, done: false }
generator.next(); // Executes line B returning { value: 2, done: false }
generator.throw(new Error("Error!!")); // Executes line D returning { value: undefined, done: true}
// console: "Error!!"
generator.next(); // no code executed. returns { value: undefined, done: true }
反復
ジェネレータは反復可能です。これはfor...of
文でループされ、反復プロトコルに依存する他の構文で使用されます。
function* range(n) {
for (let i = 0; i < n; ++i) {
yield i;
}
}
// looping
for (let n of range(10)) {
// n takes on the values 0, 1, ... 9
}
// spread operator
let nums = [...range(3)]; // [0, 1, 2]
let max = Math.max(...range(100)); // 99
ここでは、ES6のジェネレータからカスタムの反復可能なオブジェクトを使用する別の例を示します。ここでは、匿名ジェネレータ関数function *
使われています。
let user = {
name: "sam", totalReplies: 17, isBlocked: false
};
user[Symbol.iterator] = function *(){
let properties = Object.keys(this);
let count = 0;
let isDone = false;
for(let p of properties){
yield this[p];
}
};
for(let p of user){
console.log( p );
}
ジェネレータへの値の送信
next()
メソッドに渡すことで、ジェネレータに値を送ることができます 。
function* summer() {
let sum = 0, value;
while (true) {
// receive sent value
value = yield;
if (value === null) break;
// aggregate values
sum += value;
}
return sum;
}
let generator = summer();
// proceed until the first "yield" expression, ignoring the "value" argument
generator.next();
// from this point on, the generator aggregates values until we send "null"
generator.next(1);
generator.next(10);
generator.next(100);
// close the generator and collect the result
let sum = generator.next(null).value; // 111
他のジェネレータに代理
ジェネレータ関数内から、コントロールはyield*
を使って別のジェネレータ関数に委譲することができます。
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var it = g2();
console.log(it.next()); // 1
console.log(it.next()); // 2
console.log(it.next()); // 3
console.log(it.next()); // 4
console.log(it.next()); // 5
console.log(it.next()); // undefined
イテレータ - オブザーバインタフェース
ジェネレータは、 Iterator
とObserver
という2つの要素の組み合わせです。
イテレータ
反復子は、呼び出されたときにiterable
を返すものです。 iterable
はあなたが反復できるものです。 ES6 / ES2015以降、すべてのコレクション(Array、Map、Set、WeakMap、WeakSet)はIterableコントラクトに準拠しています。
ジェネレータ(イテレータ)はプロデューサです。反復において、消費者は生産者からの価値を
PULL
。
例:
function *gen() { yield 5; yield 6; }
let a = gen();
あなたが呼び出すたびa.next()
あなたは基本的にしているpull
イテレータから値を-ingとpause
で実行yield
。次回a.next()
を呼び出すと、実行は以前に一時停止した状態から再開します。
観察者
ジェネレータはまた、いくつかの値をジェネレータに戻すことができるオブザーバです。
function *gen() {
document.write('<br>observer:', yield 1);
}
var a = gen();
var i = a.next();
while(!i.done) {
document.write('<br>iterator:', i.value);
i = a.next(100);
}
ここでは、 yield 1
がある値に評価される式のように使われることがわかります。それが評価される値は、 a.next
関数呼び出しの引数として送られた値です。
したがって、 i.value
が最初にi.value
される値( 1
)になり、次の状態への反復を続けると、 a.next(100)
を使用して値をジェネレータにa.next(100)
ます。
ジェネレータとの非同期処理
ジェネレータは、関数がジェネレータを取り込み、非同期コードを同期的に書き込むことを可能にする、(taskJSまたはcoからの) spawn
関数で広く使用されていspawn
。これは、非同期コードが同期コードに変換される/同期して実行されることを意味しません。 sync
ように見えるコードを書くことはできますが、内部的にはまだasync
です。
同期はブロック中です。非同期は待ちます。ブロックするコードを書くのは簡単です。プーリングすると、割り当て位置に値が表示されます。 PUSHingの場合、コールバックの引数位置に値が表示されます。
イテレーターを使用するときは、プロデューサーから値をPULL
します。あなたがコールバックを使用すると、プロデューサーのPUSH
コールバックの引数の位置に値をES。
var i = a.next() // PULL
dosomething(..., v => {...}) // PUSH
ここでは、の値を引くa.next()
と第二に、 v => {...}
コールバックであり、値はPUSH
引数位置にED v
コールバック関数の。
このプッシュプッシュメカニズムを使用して、このような非同期プログラミングを書くことができます。
let delay = t => new Promise(r => setTimeout(r, t));
spawn(function*() {
// wait for 100 ms and send 1
let x = yield delay(100).then(() => 1);
console.log(x); // 1
// wait for 100 ms and send 2
let y = yield delay(100).then(() => 2);
console.log(y); // 2
});
だから、上記のコードを見ると、 blocking
れているような非同期コードを書いています(yieldステートメントは100ms待ってから実行を続けます)。しかし、実際にはwaiting
ます。ジェネレータのpause
とresume
プロパティは、この素晴らしいトリックを行うことができます。
どのように機能するのですか?
スポーン関数は、 yield promise
を使用して、ジェネレータからプロミスステートをPULLし、プロミスが解決されるまで待機し、解決された値をジェネレータに戻して消費するようにします。
今すぐ使用する
したがって、ジェネレータとスポーン機能を使用すると、NodeJSのすべての非同期コードを同期しているように見えるように整理できます。これにより、デバッグが容易になります。また、コードはきれいに見えます。
この機能は、 async...await
ようにJavaScriptの将来のバージョンに向けられasync...await
ます。しかし、ライブラリで定義されているspawn関数(taskjs、co、またはbluebird)を使用して、ES2015 / ES6で今日使用することができます
ジェネレータによる非同期フロー
ジェネレータは、一時停止してから実行を再開できる機能です。これにより、qやcoを中心とした外部ライブラリを使って非同期関数をエミュレートできます。基本的には、非同期の結果を待つ関数を書くことができます:
function someAsyncResult() {
return Promise.resolve('newValue')
}
q.spawn(function * () {
var result = yield someAsyncResult()
console.log(result) // 'newValue'
})
これにより、同期しているかのように非同期コードを書き込むことができます。さらに、いくつかの非同期ブロックで作業を試してみてください。約束が拒否された場合、エラーは次のキャッチによってキャッチされます。
function asyncError() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject(new Error('Something went wrong'))
}, 100)
})
}
q.spawn(function * () {
try {
var result = yield asyncError()
} catch (e) {
console.error(e) // Something went wrong
}
})
coを使うと、 q.spawn
代わりにco(function * (){...})
と全く同じようにq.spawn