ジェネレータ
ジェネレータ関数は、処理の途中で停止したり再開したりできる仕組みを持った特殊な関数です。
ジェネレータの生成
function*generatorFunc(){yield1yield2yield3}constgenerator=generatorFunc()
ジェネレータ関数には2つの明確な特徴があります。1つは、functionの後ろに*
がつくことで、もう1つはyieldキーワード
です。
function*generatorFunc(){console.log('ジェネレータ関数開始')console.log('yield 1')yield1console.log('yield 2')yield2console.log('yield 3')yield3console.log('ジェネレータ関数終了')return'ジェネレータ関数戻り値'}constgenerator=generatorFunc()console.log(generator.next())console.log(generator.next())console.log(generator.next())console.log(generator.next())console.log(generator.next())console.log(generator.next())>>>ジェネレータ関数開始yield1{value:1,done:false}yield2{value:2,done:false}yield3{value:3,done:false}ジェネレータ関数終了{value:'ジェネレータ関数戻り値',done:true}{value:undefined,done:true}{value:undefined,done:true}
ジェネレータ関数を実行するとジェネレータを返されますが、この時点ではジェネレータ関数の中の処理は実行されません。生成されたジェネレータのnext()を実行して初めて中の処理が実行されます。実行した後も全て実行されるのではなく、最初のyieldまで実行されて、そこで一時停止します。もう一度実行すると処理が再開されて2つ目のyieldまで実行します。
ジェネレータ関数の処理が完了(上記だと4回目のnext())すると{ value: '値', done: true }
(上記だと{ value: 'ジェネレータ関数戻り値', done: true }
)となります。処理終了後も実行すると{ value: undefined, done: true }
が返ってきます。
イテレータとイテラブル
ジェネレータのnext()メソッドの挙動は、イテレータプロトコルという使用に準拠しています。イテレータプロトコルは値の配列を生成するための標準的な方法を定義したもので、この仕様ではnext()メソッドがvalue, doneという2つのプロパティを含むオブジェクトを返します。
ジェネレータは、イテラブルプロトコルという仕様を満たしたイテラブルです。
イテラブルは反復可能なオブジェクトのため、for...of構文
で利用可能です。
constgenerator2=generatorFunc()for(constvofgenerator2){console.log(v)}>>>ジェネレータ関数開始yield11yield22yield33ジェネレータ関数終了
next()/throw()
- next()に引数を渡して実行
function*resetableGeneratorFunc(){letcount=0while(true){if(yieldcount++){count=0}}}constresetableGenerator=resetableGeneratorFunc()console.log(resetableGenerator.next())console.log(resetableGenerator.next())console.log(resetableGenerator.next())console.log(resetableGenerator.next(true))console.log(resetableGenerator.next())console.log(resetableGenerator.next())>>>{value:0,done:false}{value:1,done:false}{value:2,done:false}{value:0,done:false}{value:1,done:false}{value:2,done:false}
resetableGeneratorFunc()
は値をカウントアップする関数ですが、yieldの結果が真に評価された場合、カウンタの値を0にリセットします。結果として、生成されたジェネレータのnext()を真に評価される引数で実行すると、0から値を返します。
- throw()の使用
function*tryCatchGeneratoreFunc(){try{yield1}catch(err){console.log('エラーをキャッチ',err)yield2}}consttryCatchGeneratore=tryCatchGeneratoreFunc()console.log(tryCatchGeneratore.next())console.log(tryCatchGeneratore.throw(newError('エラー')))console.log(tryCatchGeneratore.next())>>>{value:1,done:false}エラーをキャッチError:エラー...(省略){value:2,done:false}{value:undefined,done:true}
エラーがジェネレータ関数ないでキャッチされなかった場合、ジェネレータは終了し、throw()自体もエラーを投げます。
try{generatorFunc.throw(newError('エラー'))}catch(err){console.log('ジェネレータ外でエラーをキャッチ',err)}>>>ジェネレータ外でエラーをキャッチTypeError:generatorFunc.throwisnotafunction...(省略)
非同期プログラミング
function*asyncWithGeneratorFunc(json){try{constresult=yieldparseJSONAsync(json)console.log('パース結果',result)}catch(err){console.log('エラーをキャッチ',err)}}// 正常系constasyncWithGenerator1=asyncWithGeneratorFunc('{"foo": 1}')constpromise1=asyncWithGenerator1.next().valuepromise1.then(result=>asyncWithGenerator1.next(result))// 異常系constasyncWithGenerator2=asyncWithGeneratorFunc('不正なJSON')constpromise2=asyncWithGenerator2.next().valuepromise2.catch(err=>asyncWithGenerator2.throw(err))>>>パース結果{foo:1}エラーをキャッチSyntaxError:Unexpectedtoken不inJSONatposition0atJSON.parse(<anonymous>)...(省略)
異常系の場合にエラーハンドリングを上記にように毎回書くのが手間なので以下のように書き直してみます。
functionhandleAsyncWithGenerator(generator,resolved){console.log(generator)const{done,value}=generator.next(resolved);if(done)returnPromise.resolve(value)returnvalue.then(resolved=>handleAsyncWithGenerator(generator,resolved),err=>generator.throw(err));}handleAsyncWithGenerator(asyncWithGeneratorFunc('{"foo": 1}'))handleAsyncWithGenerator(asyncWithGeneratorFunc('不正なJSON'))>>>パース結果{foo:1}エラーをキャッチSyntaxError:Unexpectedtoken不inJSONatposition0atJSON.parse(<anonymous>)...(省略)