情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

ECMAScript 6の新機能(25)ジェネレータ

ECMAScript 6でジェネレータという新しい概念が導入されました。

ジェネレータとは

ジェネレータとはイテレータを返す特別な関数のことです。

普通の関数を呼び出した場合には関数の処理が最初から最後まで一度に実行され、関数の処理が終了した時点で関数からの返り値が得られます。

ジェネレータの場合、ジェネレータを呼び出した際にはジェネレータの処理は一切実行されず、ジェネレータの呼び出しはすぐに終了し、ジェネレータの返り値としてはイテレータが得られます。

そして、このイテレータを通してジェネレータの処理を制御します。たとえば、処理を徐々に実行したり、処理を中断したりすることができます。

ジェネレータの作成

ジェネレータを定義する場合には関数名の前に*を付加します。

たとえば、下のようにします。

C:\work\node>node
> function *gen () {}
function *gen () {}
undefined
> gen
[Function: gen]

ただし、無名関数の場合には関数名はありませんので本来関数名を記述すべき部分に*を置きます。

> var gen2 = function * () {}
undefined
> gen2
[Function]

ジェネレータはイテレータを返すため、ジェネレータの返り値でnext関数を呼び出すことができます。

> var gobj = gen()
undefined
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
> var gobj2 = gen2()
undefined
> gobj2.next()
{ value: undefined, done: true }
> gobj2.next()
{ value: undefined, done: true }

このイテレータはイテラブルでもあります

そのため、ジェネレータの返り値に対してfor-of文などを使用することもできます。

> for (var x of gen()) {
... console.log(x);
... }
undefined

ジェネレータの本体にはどのようなイテレータを作成するかを記述します。

ジェネレータの本体には普通の関数の本体で記述できるどのような処理でも記述することができますが、それに加えてyield文やyield*文を使用することができます。

yield文に値を指定するとその値がイテレータの1つの要素となります。

たとえば、123という要素から成るイテレータを作成するには下のように記述することができます。

> function *gen3 () {
... yield 1;
... yield 2;
... yield 3;
... }
undefined
> var gobj3 = gen3()
undefined
> gobj3.next()
{ value: 1, done: false }
> gobj3.next()
{ value: 2, done: false }
> gobj3.next()
{ value: 3, done: false }
> gobj3.next()
{ value: undefined, done: true }

ジェネレータを呼び出すと返り値としてイテレータが得られますが、得られたイテレータのnext関数を呼び出した時点で初めてジェネレータの本体が最初のyield文が記述されているところまで実行されます。

再びイテレータのnext関数を呼び出すとジェネレータの本体の1つ目のyield文から2つ目のyield文までの部分が実行されます。

イテレータのnext関数を呼び出す度に同様の処理が行われ、ジェネレータの本体に記述された処理の実行が全て終了するまで続きます。

> function *gen4 () {
... console.log('foo');
... yield 1;
... console.log('bar');
... yield 2;
... console.log('baz');
... yield 3;
... console.log('qux');
... }
undefined
> var gobj4 = gen4()
undefined
> gobj4.next()
foo
{ value: 1, done: false }
> gobj4.next()
bar
{ value: 2, done: false }
> gobj4.next()
baz
{ value: 3, done: false }
> gobj4.next()
qux
{ value: undefined, done: true }
> gobj4.next()
{ value: undefined, done: true }
> gobj4.next()
{ value: undefined, done: true }

なお、yield文に値を指定しなかった場合にはイテレータの要素の値はundefinedとなります。

また、yield*文にはイテラブルを指定します。

この場合、イテラブルのそれぞれの要素をyield文に指定するのと同じ結果となります。

> function *gen5 () {
... yield 1;
... yield 2;
... yield 3;
... }
undefined
> function *gen6 () {
... yield* gen5();
... yield 99;
... yield* gen5();
... }
undefined
> var gobj6 = gen6()
undefined
> gobj6.next()
{ value: 1, done: false }
> gobj6.next()
{ value: 2, done: false }
> gobj6.next()
{ value: 3, done: false }
> gobj6.next()
{ value: 99, done: false }
> gobj6.next()
{ value: 1, done: false }
> gobj6.next()
{ value: 2, done: false }
> gobj6.next()
{ value: 3, done: false }
> gobj6.next()
{ value: undefined, done: true }

なお、yield*文には必ずイテラブルを指定しなければなりません。

return

ジェネレータの本体でreturn文を使用した場合にはreturn文に到達した時点でジェネレータの実行は終了となります。

C:\work\node>node
> function *gen () {
... yield 1;
... yield 2;
... return;
... yield 3;
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.next()
{ value: 2, done: false }
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }

ただし、return文で返り値を指定した場合にはその返り値はイテレータで全ての要素が取り出された後の最初のnext関数の呼び出しにおけるvalueプロパティの値となります。

この時、doneプロパティはtrueとなることに注意してください

C:\work\node>node
> function *gen () {
... yield 1;
... yield 2;
... return 'xxx';
... yield 3;
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.next()
{ value: 2, done: false }
> gobj.next()
{ value: 'xxx', done: true }
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }

そのため、return文による返り値はイテラブルの要素ではありません

> function *gen () {
... yield 1;
... yield 2;
... return 'xxx';
... yield 3;
... }
undefined
> for (x of gen()) {
... console.log(x);
... }
1
2
undefined

なお、yield*文の返り値は指定したイテラブルのreturn文による返り値となります。

C:\work\node>node
> function *gen () {
... yield 1;
... return 'xxx';
... yield 2;
... }
undefined
> function *gen2 () {
... console.log(yield* gen());
... yield 99;
... console.log(yield* gen());
... }
undefined
> var gobj2 = gen2()
undefined
> gobj2.next()
{ value: 1, done: false }
> gobj2.next()
xxx
{ value: 99, done: false }
> gobj2.next()
{ value: 1, done: false }
> gobj2.next()
xxx
{ value: undefined, done: true }
> gobj2.next()
{ value: undefined, done: true }

next関数

next関数には1つだけ引数を指定することもできます。

この引数の値はnext関数の呼び出しに対応するyield文の返り値となります。

C:\work\node>node
> function *gen () {
... console.log(yield 1);
... console.log(yield 2);
... console.log(yield 3);
... }
undefined
> var gobj = gen()
undefined
> gobj.next('a')
{ value: 1, done: false }
> gobj.next('b')
b
{ value: 2, done: false }
> gobj.next('c')
c
{ value: 3, done: false }
> gobj.next('d')
d
{ value: undefined, done: true }
> gobj.next('e')
{ value: undefined, done: true }
> gobj.next('f')
{ value: undefined, done: true }

このように、n回目のnext関数の呼び出しに対応するのはn - 1個目のyield文であることに注意してください。1回目のnext関数の呼び出しの場合、対応するyield文は存在しません。そのため、1回目のnext関数の呼び出しの際に指定した値は無視されます。

return関数

ジェネレータから得られたイテレータのreturn関数を呼び出した場合にはイテレータの要素の取得はその時点で終了となります。

C:\work\node>node
> function *gen () {
... yield 1;
... yield 2;
... yield 3;
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.return()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
> gobj.return()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }

return関数には1つだけ引数を指定することもできます。

この場合、return関数の返り値として得られるオブジェクトのvalueプロパティに引数の値が設定されます。

C:\work\node>node
> function *gen () {
... yield 1;
... yield 2;
... yield 3;
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.return('foo')
{ value: 'foo', done: true }
> gobj.next()
{ value: undefined, done: true }
> gobj.return('foo')
{ value: 'foo', done: true }
> gobj.next()
{ value: undefined, done: true }

throw関数

ジェネレータから得られたイテレータのthrow関数を呼び出した場合には例外が発生し、イテレータの要素の取得はその時点で終了となります。

なお、この関数の第1引数にはエラーオブジェクトを指定しなければなりません

C:\work\node>node
> function *gen () {
... yield 1;
... yield 2;
... yield 3;
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.throw(new Error('error'))
Error: error
    at repl:1:12
    at REPLServer.defaultEval (repl.js:272:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:441:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
    at REPLServer.Interface._onLine (readline.js:224:10)
    at REPLServer.Interface._line (readline.js:566:8)
    at REPLServer.Interface._ttyWrite (readline.js:843:14)
> gobj.next()
{ value: undefined, done: true }
C:\work\node>node
> function *gen () {
... try {
..... yield 1;
..... yield 2;
..... yield 3;
..... } catch (err) {
..... console.error(err);
..... }
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.throw(new Error('error'))
Error: error
    at repl:1:12
    at REPLServer.defaultEval (repl.js:272:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:441:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
    at REPLServer.Interface._onLine (readline.js:219:10)
    at REPLServer.Interface._line (readline.js:561:8)
    at REPLServer.Interface._ttyWrite (readline.js:838:14)
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }

ただし、引数にエラーオブジェクト以外のものを指定した場合には例外は発生しませんので注意してください。

C:\work\node>node
> function *gen () {
... yield 1;
... yield 2;
... yield 3;
... }
undefined
> const gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.throw(1)
1
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
> gobj.throw()
undefined
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
> gobj.throw('sk1')
sk1
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }

try...catch...finally

C:\work\node>node
> function *gen () {
... console.log('a');
... yield 1;
... yield 2;
... yield 3;
... console.log('b');
... try {
..... console.log('c');
..... yield 4;
..... yield 5;
..... yield 6;
..... console.log('d');
..... } catch (err) {
..... console.log('e');
..... yield 7;
..... yield 8;
..... yield 9;
..... console.log('f');
..... } finally {
..... console.log('g');
..... yield 10;
..... yield 11;
..... yield 12;
..... console.log('h');
..... }
... console.log('i');
... yield 13;
... yield 14;
... yield 15;
... console.log('j');
... }
undefined
> var gobj = gen()
undefined
> gobj.next()
a
{ value: 1, done: false }
> gobj.next()
{ value: 2, done: false }
> gobj.next()
{ value: 3, done: false }
> gobj.next()
b
c
{ value: 4, done: false }
> gobj.next()
{ value: 5, done: false }
> gobj.next()
{ value: 6, done: false }
> gobj.next()
d
g
{ value: 10, done: false }
> gobj.next()
{ value: 11, done: false }
> gobj.next()
{ value: 12, done: false }
> gobj.next()
h
i
{ value: 13, done: false }
> gobj.next()
{ value: 14, done: false }
> gobj.next()
{ value: 15, done: false }
> gobj.next()
j
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
C:\work\node>node
> function *gen () {
... try {
..... yield 1;
..... yield 2;
..... } catch (err) {
..... yield 3;
..... yield 4;
..... } finally {
..... yield 5;
..... yield 6;
..... }
... }
undefined
> var gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.return('a')
{ value: 5, done: false }
> gobj.return('b')
{ value: 'b', done: true }
> gobj.return('c')
{ value: 'c', done: true }
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
> var gobj2 = gen()
undefined
> gobj2.next()
{ value: 1, done: false }
> gobj2.return('a')
{ value: 5, done: false }
> gobj2.next()
{ value: 6, done: false }
> gobj2.next()
{ value: 'a', done: true }
> gobj2.next()
{ value: undefined, done: true }
> gobj2.next()
{ value: undefined, done: true }
C:\work\node>node
> function *gen () {
... try {
..... yield 1;
..... yield 2;
..... } catch (err) {
..... yield 3;
..... yield 4;
..... } finally {
..... yield 5;
..... yield 6;
..... }
... }
undefined
> var gobj = gen()
undefined
> gobj.next()
{ value: 1, done: false }
> gobj.throw(new Error('error'))
{ value: 3, done: false }
> gobj.throw(new Error('error'))
{ value: 5, done: false }
> gobj.throw(new Error('error'))
Error: error
    at repl:1:12
    at REPLServer.defaultEval (repl.js:272:27)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:441:10)
    at emitOne (events.js:101:20)
    at REPLServer.emit (events.js:188:7)
    at REPLServer.Interface._onLine (readline.js:224:10)
    at REPLServer.Interface._line (readline.js:566:8)
    at REPLServer.Interface._ttyWrite (readline.js:843:14)
> gobj.next()
{ value: undefined, done: true }
> gobj.next()
{ value: undefined, done: true }
pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-ecmascript 2015, ecmascript 6, Javascript