[Javascript] ES6のジェネレーターを使う
こんにちは、@yoheiMuneです。
Javascriptでもジェネレーターが使えるようになってきました。今日は使い方をブログに書きたいと思います。
https://github.com/yoheiMune/frontend-playground/blob/master/021-generator/sample.js
フロントエンドで大量データを扱うことは稀ですが、ジェネレーターを使えるようになっておくと、自分の引き出しが増えていいかなと思います(フロントエンドだと「ES7のasync/await」がジェネレーターのシンタックスシュガーになっています)。
具体的な実装を見ていきたいと思います。
また、ジェネレータは
これが基本的な使い方です。
以下の場合には、ジェネレーター内で例外処理をしているので、例外が通知されても処理は引き続き続行されます。
逆に以下の場合には、ジェネレーター内で例外処理をしないので、呼び出し元に例外が返って来ます。そしてジェネレーターは終了します。
- Generator - JavaScript | MDN
- イテレーターとジェネレーター - JavaScript | MDN - Iterators and generators - JavaScript | MDN
- function* - JavaScript | MDN
本ブログでは、フロントエンド、Node.js、Go言語、Python、Linux、インフラ、Swift、Java、機械学習、などの技術トピックを発信をしていきます。「プログラミングで困ったその時に、解決の糸口を見つけられる」そんな目標でブログを書き続けています。今後も役立つネタを書いていきますので、ぜひ本ブログのRSSやTwitterをフォローして貰えたら嬉しいです ^ ^
最後までご覧頂きましてありがとうございました!
Javascriptでもジェネレーターが使えるようになってきました。今日は使い方をブログに書きたいと思います。
目次
サンプル実装
この記事にある実装内容は、以下にまとまっています。適宜ご参照いただけたら嬉しいです。https://github.com/yoheiMune/frontend-playground/blob/master/021-generator/sample.js
ジェネレーターとは
Pythonなど他の言語にはある機能で、データをちょっとずつ処理できるような機能を提供します。例えば以下のように使います。
// ジェネレーター関数
function *readFiles() {
for (let i = 0; i < 100; i++) {
// ファイルを1つ読み込んでは、呼び出し元に返す.
const file = readFile(i)
yield file
}
}
// ジェネレーターを使う.
for (file of readFiles()) {
// 1つずつファイルを処理する.
consume(file)
}
上記の例では、100個のファイルを読み込んで処理する際に、ジェネレーターを使っています。ジェネレーターを使うことで1ファイルずつ処理することができます。100個全てのファイルを同時に読み込まないので、メモリに優しい実装です。フロントエンドで大量データを扱うことは稀ですが、ジェネレーターを使えるようになっておくと、自分の引き出しが増えていいかなと思います(フロントエンドだと「ES7のasync/await」がジェネレーターのシンタックスシュガーになっています)。
具体的な実装を見ていきたいと思います。
ジェネレーターを使う
具体的な実装を見ていきたいと思います。基本的な使い方
ジェネレーターの関数は、関数定義で*を付与して定義します。そして値を返したいところでyield構文を利用します。
// ジェネレーターを定義する
function* gen () {
yield 1
yield 2
yield 3
}
上記のジェネレーターは以下のように呼び出すことができます。
// ジェネレーターを使う
var g = gen()
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
next関数を呼び出すことで1つずつ値を取りダウことができ、返却内容のdoneから、ジェネレーターが終了したのかを知ることができます。また、ジェネレータは
for-of構文でも利用することができます。
// ジェネレーターを使う(for-of構文)
for (item of gen()) {
console.log(item)
}
// 1
// 2
// 3
for-of構文の場合には、値のみが返ってきます(doneは返却されない)。これが基本的な使い方です。
ジェネレーターの中にジェネレーターを含める
ジェネレーターの中に、さらにジェネレーターを含めることができます。yield*構文を用います。
// ジェネレーターの定義
function* gen2 () {
yield* ['a', 'b', 'c']
yield* gen()
}
// 呼び出す
for (item of gen2()) {
console.log(item)
}
// a
// b
// c
// 1
// 2
// 3
呼び出し側はジェネレーターの中身がどうなっているのかは気にすることなく、1つずつ値を取り出すことができます。ジェネレーターに値を戻す
ジェネレーターの呼び出し側は、next()関数の引数に値を指定することで、ジェネレーターに値を返すことができます。
function* calc () {
let v = 1
while (true) {
v = v + v
// 返って来た値を処理する.
reset = yield v
if (reset) {
v = 1
}
}
}
console.log('---')
var g = calc()
console.log(g.next().value) // 2
console.log(g.next().value) // 4
console.log(g.next().value) // 8
// 値を返すことができる.
console.log(g.next(true).value) // 2
console.log(g.next().value) // 4
上記では計算結果をリセットしていますが、next関数に値を指定することで、値をジェネレーターに渡すことができます。ジェネレーターに例外を通知する
値を返す他に、例外を送り込むこともできます。以下の場合には、ジェネレーター内で例外処理をしているので、例外が通知されても処理は引き続き続行されます。
// ジェネレーター関数(例外処理あり)
function* myGen () {
while (true) {
try {
yield 'good'
} catch (e) {
console.log(e.message)
}
}
}
var g = myGen()
console.log(g.next().value) // good
var result = g.throw(new Error('Oh My Error...')) // Oh My Error...
console.log(result) // { value: 'good', done: false } <= 例外通知後も「good」で「done=false」
console.log(g.next().value) // good
逆に以下の場合には、ジェネレーター内で例外処理をしないので、呼び出し元に例外が返って来ます。そしてジェネレーターは終了します。
// ジェネレーター関数(例外処理なし)
function* myGen () {
while (true) {
yield 'good'
}
}
var g = myGen()
console.log(g.next().value) // good
try {
var result = g.throw(new Error('Oh My Error...'))
} catch (e) {
// ジェネレーターが処理しないので、例外がここに入る.
console.log(e.message) // Oh My Error...
}
console.log(g.next()) // { value: undefined, done: true } <= 「done=true」で完了状態になる.
このあたりの機能を使うことは稀かなと思いますが・・・、お勉強ということでサンプル実装を書いてみました。参考資料
今回の記事を書くために、以下の資料を参照しました。ありがとうございます。- Generator - JavaScript | MDN
- イテレーターとジェネレーター - JavaScript | MDN - Iterators and generators - JavaScript | MDN
- function* - JavaScript | MDN
最後に
新しい機能を学ぶと視野も広がっていいですね。Javascriptのイテレーターも使う機会があれば使ってみようと思います。Pythonなどでは時々ジェネレーターを使っています。大量のファイルやリクエストを扱う場合に、一度にメモリに載せると非効率(またはメモリに載らない)な場合に、ジェネレーターでちょっとずつ処理しています。本ブログでは、フロントエンド、Node.js、Go言語、Python、Linux、インフラ、Swift、Java、機械学習、などの技術トピックを発信をしていきます。「プログラミングで困ったその時に、解決の糸口を見つけられる」そんな目標でブログを書き続けています。今後も役立つネタを書いていきますので、ぜひ本ブログのRSSやTwitterをフォローして貰えたら嬉しいです ^ ^
最後までご覧頂きましてありがとうございました!






