[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をフォローして貰えたら嬉しいです ^ ^
最後までご覧頂きましてありがとうございました!