大阪node学園 六時限目 「generator小咄」
DESCRIPTION
大阪Node学園 六時限目 「generator小咄」のスライドです サンプルコードはgithubにあります https://github.com/craftgear/ong6TRANSCRIPT
大阪Node学園六時限目
generator小咄
渡辺俊輔 @craftgearhttp://blog.craftgear.net/
Agenda1. generator = 同期風コード?
2. generatorってなんだ
3. generatorとiterator4. 同期風コードの書き方
5. generatorベースのライブラリ群
6. generatorを使うにあたって考慮したいこと
7. 今日のまとめ
8. 今後の予定
9. 参考文献
10. About the Author
generator = 同期風コード?
とあるブログにて
node v0.12 (= node v1.0 ?) からgenerator が使えるよ
うになるらしい。
ほほう、どれどれ
( ´_ゝ`)
ブログ記事の要約(IMHO)
ここにgeneratorがある ゃろ?
( ^ω^)generator
これをこうして…( ^ω^)
≡ ≡
こう ゃ
( ^ω^)同期っぽい!
( ゚д゚)え?
generator ≠ 同期風コード
generator + α = 同期風コード
generatorってなんだ
関数の一種
generatorもしくはgenerator関数とも
通常の関数
↓
generator
function* ←アスタリスク重要!途中で値を返すのにはyieldを使う
function hoge() {
return 'hoge';
}
1
2
3
function* hoge() {
yield 'hoge';
}
1
2
3
node v0.11.x ではオプションが必要
node hoge.js↓
node --harmony-generators hoge.js
戻り値が違う
通常の関数 実行結果
↓
generator 実行結果
戻り値がiteratorオブジェクト
&iteratorについてはのちほど'
function hoge() {
return 'hoge';
}
var result = hoge();
console.log(result);
1
2
3
4
5
6
hoge1
function* hoge() {
yield 'hoge';
}
var result = hoge();
console.log(result, typeof result);
1
2
3
4
5
6
{} object1
https://github.com/craftgear/ong6/tree/master/example/01_return_value.js
generatorは関数の一種
アスタリスク重要
途中で値を返すのには yield を使う
node v0.11.x ではオプションが必要
戻り値がiteratorオブジェクト
generatorとiterator
iteratorとは
GoFのあれ
iteratorもしくはgenerator iteratorとも
generatorを実行した時の戻り値
iteratorはgeneratorを操作するためのもの
next() メソッドを呼び出すと yieldまで処理が走って止まる
処理を再開するには再度next()を呼ぶ
サンプルコード
iteratorはgeneratorのリモコン
next() メソッドはリモコンの再生ボタン
function* hoge(){
yield 1;
yield 2;
//return 3;
}
var iterator = hoge();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
1
2
3
4
5
6
7
8
9
10
11
https://github.com/craftgear/ong6/tree/master/example/02_iterator.js
next()でgeneratorの内部状態がわかる
next()の戻り値は value と done の二つのプロパティを持ったオ
ブジェクト
value ヹヹヹ yield の右側の値が入る
done ヹヹヹ generatorの最後に達するか、明示的にreturn 文に出会うと true になる
サンプルコード 実行結果
next()メソッドの戻り値で、generator内部の状
態がわかる
function* hoge(){
yield 1;
yield 2;
//return 3;
}
var iterator = hoge();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
1
2
3
4
5
6
7
8
9
10
11
{ value: 1, done: false }
{ value: 2, done: false }
{ value: undefined, done: true }
//return 3;のコメントアウトを外した場合
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: true }
1
2
3
4
5
6
7
8
https://github.com/craftgear/ong6/tree/master/example/02_iterator.js
その逆も出来る
generator内部の状態を知るだけでなく、next()メソッドで
generator内部の状態を操作できる
サンプルコード
実行結果
next()メソッドに引数を渡すと、generatorに値
を送りこめる
function* hoge(){
var x = null;
console.log('inside generator: x is ', x);
x = yield 1;
console.log('inside generator: after yielded, x is ', x);
}
var iterator = hoge();
console.log('return value of next', iterator.next());
console.log('return value of next', iterator.next('hoge'));
1
2
3
4
5
6
7
8
9
10
11
inside generator: x is null
return value of next { value: 1, done: false }
inside generator: after yielded, x is hoge
return value of next { value: undefined, done: true }
1
2
3
4
https://github.com/craftgear/ong6/tree/master/example/03_next.js
next()を呼び出した時に起こること
iterator操作側: 10行目 next()メソッドを呼び出す
generator内部: yieldのある行(4行目)まで実行され、
yieldの右側の値が戻り値のvalueプロパ
ティとして返される
処理がここでとまる
iteretor操作側: 11行目でふたたびnext()メソッドを呼び
出す
generator内部: 先ほど止まったyieldのある行(4行目)から実行が再開される。このとき、nextの引
数が yield文の評価結果になる
すなわち、4行目のyield 1 が hoge におき
かわり、xにhogeが代入される
yieldが返す値とyield文の評価結
果、この二つの区別が大事
iteratorはgeneratorのリモコン
next() はリモコンの再生ボタン
next() の戻り値はgeneratorの内部状態
next() メソッドに引数を渡すと、generator内部
に値を送り込める
yieldが返す値とyield文の評価結果、この二
つの区別が大事
同期風コードの書き方
コールバックスタイル
var fs = require('fs');
fs.readFile(
__dirname + '/ong6.txt'
, {encoding: 'utf-8'}
, function (error, result) {
console.log(result);
process.exit();
}
);
1
2
3
4
5
6
7
8
9
10
https://github.com/craftgear/ong6/tree/master/example/04_async.js
generatorスタイル&仮'
var fs = require('fs');
var fileReader = function* (callback){
var content = yield fs.readFile(
__dirname + '/ong6.txt'
, {encoding: 'utf-8'}
, callback
);
console.log('generator: ', content);
process.exit();
}
function run(generator){
var iterator = null;
var callback = function(err, data) {
if(err) iterator.throw(err);
iterator.next(data);
};
iterator = generator(callback);
iterator.next();
}
run(fileReader);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
https://github.com/craftgear/ong6/tree/master/example/05_sync.js
同期風コード
generatorスタイル&仮'処理の流れ
1. 25行目から処理開始
2. 21行目でgeneratorにコールバック用関数を渡してiterator生成
3. 22行目のiterator.next()で4行目のyieldの右辺が実行され
る
4. fs.readFile処理が走り、16行目のコールバック用の関数に結
果が渡る
5. 18行目でiterator.nextを呼び出す、読み込んだファイルの中
身を引数に渡して呼び出す
6. 4行目から処理が再開され、resultには読み込んだファイルの
中身が入る
7. 9行目で結果を表示する
8. 10行目で処理終了
https://github.com/craftgear/ong6/tree/master/example/05_sync.js
generatorで同期風コードを実現するには
generatorとiteratorを操作するコードの二つが
必要
generator内部のコード&自分が実行したい処理'
iteratorを回すコード&generatorを制御するコード'
の二つに分けて考えるようにするとわかりやすい
+αの中身はiteratorの操作コード
generatorスタイル&仮'の問題点
iteratorを制御するコードを書かないといけない
yieldが一回しか使えない
エラー処理がない
しかしこれらの処理は一度書けばすむは
↓
それnpmで
generatorで同期風コードを実現するには
generatorとiteratorを操作するコードの二つが
必要
こうしてこう ゃ の中身はiteratorの操作
コード
自分で書か にライブラリを使おう!
generatorベースのライブラリ群
その1 - suspendhttps://github.com/jmar777/suspend
suspendを使った同期風コード
var suspend = require('suspend')
, fs = require('fs');
suspend(function* (resume){
try{
var content = yield fs.readFile(
__dirname + '/ong6.txt'
, {encoding: 'utf-8'}
, resume
);
console.log('generator: ', content);
}
catch(e){
console.log(e);
}
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
https://github.com/craftgear/ong6/tree/master/example/06_suspend.js
suspendの特徴
generatorを引数に渡して実行した後、再度実行する必要が
ある (16行目)nodeの標準コールバックスタイルがそのまま使える
Promiseとも併用できる
スタックトリースでエラーを起こしたyield文の場所がわかる
try catchするとエラーの場所がわからなくなる
その2 - cohttps://github.com/visionmedia/co
coを使った同期風コード
var co = require('co')
, fs = require('fs');
var read = co.wrap(fs.readFile);
co(function* (){
try{
var content = yield read(
__dirname + '/ong6.txt'
, {encoding: 'utf-8'}
);
console.log('generator: ', content);
}
catch(e){
console.log(e);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
https://github.com/craftgear/ong6/tree/master/example/07_co.js
coの特徴
呼び出す関数をラップするか、もしくはThunk(次ページ参照)にする必要がある &4行目'
Promiseとも併用できる
generatorを引数に渡すだけでよい
スタックトリースでエラーの場所がわからない
expressのミドルイェア用モジュールがある
TJイェア
Thunkってなに?
A "thunk" is also known as a "continuable"and is simply a function that accepts a node
style callback as it's only argument.訳: "thunk"は"continuable"ともいわれ、
node形式のコールバックのみを引数として
け る関数です。
gen-runのREADME.mdより
function sleep(ms) {
return function (callback) {
setTimeout(callback, ms);
};
}
その3 - gennyhttps://github.com/spion/genny
gennyを使った同期風コード
var genny = require('genny')
, fs = require('fs');
//genny.longStackSupport = true;
genny.run(function* (resume){
try{
var content = yield fs.readFile(
__dirname + '/ong6.txt'
, {encoding: 'utf-8'}
, resume()
);
console.log('generator: ', content);
}
catch(e){
console.log(e);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
https://github.com/craftgear/ong6/tree/master/example/08_genny.js
gennyの特徴
suspendと似ているが最後の関数実行は必要ない &18行目'
そのかわりコールバック用関数を実行して渡す必要がある
&11行目'
ThunkやPromiseとも併用できる
スタックトリースでエラーを起こしたyield文の場所がわかる
try catchするとエラーの場所がわからなくなる
longStackTraceオプションを有効にするとtry catchしてもエラ
ーの場所がわかる
ただしlongStackTraceオプションはオーバーヘッドがすごい
expressのミドルイェアをgeneratorにできる
genny.middleware がある
その4 - gen-runhttps://github.com/creationix/gen-run
gen-runを使った同期風コード
var run = require('gen-run')
, fs = require('fs');
run(function* (resume){
try{
var content = yield fs.readFile(
__dirname + '/ong6.txt'
, {encoding: 'utf-8'}
, resume()
);
console.log('generator: ', content);
}
catch(e){
console.log(e);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
https://github.com/craftgear/ong6/tree/master/example/09_gen-run.js
gen-runの特徴
coと同 ようなThunkとsuspendのようなコールバックスタイル
の両方が使える
スタックトリースでエラーを起こしたyield文の場所がわかる
try catchするとエラーの場所がわからなくなる
generatorベースのライブラリを使うと
同期風コードが書ける
try catchでエラー処理が書ける
デバッグがしづらくなる
generatorを使うにあたって考慮し
たいこと
nodeの方針
by Isaac
コールバックスタイルの理解は必須
The Future of Programming in Node.js
"Callbacks will remain the de facto way toimplement asynchrony. Generators and
Promises are interesting and will remain auserland option."
訳: コールバックがデファクトの非同期実
装方法で在り続けます。ジェネリータやプロ
ミスは興味深いですが、ユーザーランドの選
択肢のままです。
ライブラリのオーバーヘッド
gennyの作者が書いたライブラリの比較記事
各ライブラリの実行速度、デバッグのしやすさなどあり
ライブラリごとにばらつきはあるが、リクエストごとの処理時間で
160%〜400%、メモリ使用量で140%〜240%のオーバーヘッ
ドあり
generatorとは関係ないけどPromiseベースのライブラリが異常
に遅い
オーバーヘッドあり
Analysis of generators and other async patterns in node
コールバックは捨てられない
ライブラリ利用のオーバーヘッドを織り込
んでおく
今日のまとめ
generatorベースのライブラリを使うと同期風
コードが書ける
ライブラリを使うだけならgeneratorそのもの
を理解してなくても大丈夫
generatorベースのライブラリを使うとオーバ
ーヘッドがある
他のライブラリと同 ように便利なら使う
プロジェクトごとに非同期処理の方針が
あるといいかも
今後の予定
大阪Node学園七時限目 ゼロから始めるnode.js
大阪Node学園八時限目 タイトル未定
9/28 JAWS FESTA Kansai 2013 出張版
10/28 Innovation EGG 第一回勉強会 出張版
参考文献
イテリータとジェネリータ - JavaScript | MDNgenerators in v8 -- wingologA Study on Solving Callbacks with JavaScript GeneratorsA Closer Look at Generators Without Promisesjmar777/suspendvisionmedia/cospion/gennycreationix/gen-runThe Future of Programming in Node.js - Google グループ
Analysis of generators and other async patterns in node
About the Author
渡辺俊輔
フリーランスWebエンジニア
質問、訂正などはgoogle+、twitterまたはメールでどう
google+ 大阪node学園
twitter@[email protected]