東京NODE学園 1時限目
「非同期プログラミングの改善」のエッセンス
小林浩一 @koichikhttp://d.hatena.ne.jp/koichik/
自己紹介
後で(ry
Nodeの本を書いてます
タイトル未定?
最初から最後までNode
JSの基本とか他のSSJSとか一切なし
Node Nodeの基本から応用まで盛りだくさん
500ページ級?
発売時期?
本当はもうすぐ出るはずだったけど・・・
Node本の構成
導入編
基本編
実践編
応用編
Node本の構成
導入編
基本編
実践編
応用編
非同期プログラミングの改善
Node本の構成
導入編
基本編
実践編
応用編
非同期プログラミングの改善
のエッセンス
Agenda
非同期プログラミング
非同期APIのスタイル
非同期プログラミングの問題
イディオム イディオム
アクターとコールバックの分離
非同期APIのスタイル
イベントリスナ・スタイル
net, httpモジュール
コールバック・スタイル
fs, dnsモジュール fs, dnsモジュール
イベントリスナ・スタイル
EventEmitterのサブクラスを使う
イベントハンドラを登録する
var server = net.createServer();var server = net.createServer();server.on('request', function(socket) {
...});server.on('error', function(err) {
...});
コールバック・スタイル
最後の引数でコールバック関数を渡す
コールバック関数の最初の引数はエラー
fs.readFile('foo.txt', function(err, data) {fs.readFile('foo.txt', function(err, data) {...
});
幻のプロミス
コールバックの前はプロミスだった
var promise = posix.rename("/tmp/hello", "/tmp/world");promise.addCallback(function() {
...}).addErrback(function() {
プロミスの発展・応用系が Deferred Nodeのプロミスはチェーンもできた
しかし品質が低かった 標準モジュールはもっとシンプルに→コールバック
}).addErrback(function() {...
});
@masuidriveの悲劇
2010/02/13
@masuidrive、プロミスのパッチを送る
2010/02/02/17
Node v0.1.29 リリース Node v0.1.29 リリース
パッチが採用される
2010/02/22
Node v0.1.30 リリース
プロミス消滅
非同期プログラミングの問題
イベントリスナスタイルは問題ではない
理由は書籍で!
問題はコールバック・スタイル
コールバック・スタイル
この同期APIに・・・try {
data = fs.readFileSync('foo.txt');... // ここで data を扱う
} catch (err) {} catch (err) {... // エラー処理
}
コールバック・スタイル
対応する非同期APIはこう
fs.readFile('foo.txt', function(err, data) {if (err) {
... // ここでエラー処理... // ここでエラー処理(return or throw)
}... // ここで data を扱う
});
非同期プログラミングの問題
深いネスト// 同期var x = f();var y = g(x);var z = h(y);
// こうも書けるvar z = h(g(f(x)));...
var z = h(y);...
// 非同期f(function(err, x) {
g(x, function(err, y) {h(y, function(err, z) {
...});
});});
非同期プログラミングの問題
無名関数をやめても・・・f(gotX);function gotX(x) {
g(x, gotY);} 名前でジャンプするなんてgoto文でしょ}function gotY(y) {
h(y, gotZ);}function gotZ(z) {
...}
名前でジャンプするなんてgoto文でしょ
非同期プログラミングの問題
エラーハンドリング (同期)
try {var z = h(g(f(x)));... // 成功時の処理
} catch (err) {} catch (err) {... // エラー処理はここでまとめて
}
非同期プログラミングの問題
エラーハンドリング (非同期)try {
f(function(x) {g(x, function(y) {
h(y, function(z) {h(y, function(z) {... // 成功時の処理
});});
});} catch (err) {... // エラー処理?
}間違い!
非同期プログラミングの問題
エラーハンドリング (非同期)f(function(err, x) {
if (err) {... // f() のエラー処理return;
}}g(x, function(err, y) {
if (err) {... // g() のエラー処理return;
}h(y, function(err, z) {
if (err) {... // h() のエラー処理return;
}... // 成功時の処理
});});
});
非同期プログラミングの問題
無名関数を使うとネストが深くなる
無名関数を使わなければgotoもどきになる
エラー処理が散在する
Agenda
非同期プログラミング
非同期API
非同期プログラミングの問題
イディオム イディオム
アクターとコールバックの分離
例
同期版function toUpperCaseFile(path) {
try {var stats = fs.statSync(path);if (!stats.isFile()) throw new Error(path + ' is not a file');if (!stats.isFile()) throw new Error(path + ' is not a file');var data = fs.readFileSync(path, 'utf8');fs.writeFileSync(path, data.toUpperCase());console.log('completed');
} catch (err) {console.error(err);
}}
例
非同期版function toUpperCaseFile(path) {
fs.stat(path, function(err, stats) {if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', function(err, data) {
if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), function(err) {
if (err) return console.error(err);console.log('completed');
});});
});}
インラインの無名関数をやめるfunction toUpperCaseFile(path) {
fs.stat(path, readFile);function readFile(err, stats) {
if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', writeFile);
}}function writeFile(err, data) {
if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), complete);
}function complete(err) {
if (err) return console.error(err);console.log('completed');
}}
やりたいことは何か?
ネストを深くしたくない
コールバックをインライン (無名関数) にしなければよい
ラベル (関数名) に頼りたくない
コールバックを無名関数にすればよい
やりたいことは何か?
ネストを深くしたくない
コールバックをインライン (無名関数) にしなければよい
ラベル (関数名) に頼りたくない
コールバックを無名関数にすればよい
矛盾!
非インラインの無名関数にしてみる
function toUpperCaseFile(path) {fs.stat(path, ????);function(err, stats) {
if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', ????);fs.readFile(path, 'utf8', ????);
}function(err, data) {
if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), ????);
}function(err) {
if (err) return console.error(err);console.log('completed');
}}
何が起きたか?
無名関数を非同期APIのコールバックとして渡せなくなった
コールバックはどうする?何を渡す?
そこで!
無名関数とコールバックを分離する
コールバックの役割
「次」の無名関数を呼び出す
無名関数の役割
アプリケーション固有の処理
非同期APIを呼び出す
これを「アクター」と呼ぶ
Erlang他のアクターとは無関係
アクターとコールバックを結びつける
誰が?
ライブラリ
フロー制御モジュールと呼ばれる
複数のアクターを受け取る 複数のアクターを受け取る
配列 or 可変長引数 (arguments)
アクターにコールバックを提供する
コールバックはアクターを順次呼び出す
フロー制御モジュールのイメージ
chain(function(next) {fs.stat(path, next);
}, function(err, stats, next) {if (err) return console.error(err);if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', next);
}, function(err, data, next) {if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), next);
}, function(err) {if (err) return console.error(err);console.log('completed');
});
フロー制御モジュールの実装
function chain() {var actors = Array.prototype.slice.call(arguments);next();function next() {function next() {
var actor = actors.shift();var args = Array.prototype.slice.call(arguments);if (actors.length > 0) { //最後のアクターにはnextを渡さない
args = args.concat(next);}actor.apply(null, args);
}}
結果
たった12行の関数で
これや
function toUpperCaseFile(path) {fs.stat(path, function(err, stats) {
if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', function(err, data) {
if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), function(err) {
if (err) return console.error(err);console.log('completed');
});});
});}
これが
function toUpperCaseFile(path) {fs.stat(path, readFile);function readFile(err, stats) {
if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', writeFile);fs.readFile(path, 'utf8', writeFile);
}function writeFile(err, data) {
if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), complete);
}function complete(err) {
if (err) return console.error(err);console.log('completed');
}}
こうなった
chain(function(next) {fs.stat(path, next);
}, function(err, stats, next) {if (err) return console.error(err);if (err) return console.error(err);if (!stats.isFile()) return console.error(path + ' is not a file');fs.readFile(path, 'utf8', next);
}, function(err, data, next) {if (err) return console.error(err);fs.writeFile(path, data.toUpperCase(), next);
}, function(err) {if (err) return console.error(err);console.log('completed');
});
効果
ネストは深くならない
無駄な名前に頼らない
課題
エラーハンドリングは?
エラー時のルーティング
アクターごとにエラー処理をしたくない
エラーが起きたら途中のアクターをすっ飛ばそう
最後のアクターでまとめてエラー処理 最後のアクターでまとめてエラー処理
try~catch と同じ
chain()の改善
function chain() {var actors = Array.prototype.slice.call(arguments);next();function next(err) {function next(err) {
if (err) return actors.pop()(err); // エラーなら最後のアクターへvar actor = actors.shift();var args = Array.prototype.slice.call(arguments);if (actors.length > 0) { // 最後のアクターにはnextを渡さない
args = args.slice(1).concat(next); // err は渡さない}actor.apply(null, args);
}}
結果
たった13行の関数で
こうなった
function toUpperCaseFile(path) {chain(function(next) {
fs.stat(path, next);}, function(stats, next) {}, function(stats, next) {
if (!stats.isFile()) return next(path + ' is not a file');fs.readFile(path, 'utf8', next);
}, function(data, next) {fs.writeFile(path, data.toUpperCase(), next);
}, function(err) {if (err) return console.error(err);console.log('completed');
});}
課題
無名関数大杉ね?
一行ばっかだし
続きは書籍で!
別のイディオムも!
すぐに使えるフロー制御モジュールの紹介も!
まとめ
コールバックスパゲッティ?
ちゃんちゃらおかしいね
いくらでも工夫できる!
自分のフロー制御モジュールを作ってみよう! 自分のフロー制御モジュールを作ってみよう!
世界のデファクトになるかもよ!?
先人達の工夫 https://github.com/joyent/node/wiki/modules#async-flow
CSJSや他言語のアイディアも活用しよう
Deferred, ReactiveExtension, ファイバ, ...
非同期プログラミングは怖くないよ怖くないよ
Q&A
ご質問があればどうぞ!
ご清聴ありがとうございました
こんなこともあろうかと
自己紹介
小林浩一
@koichik
http://d.hatena.ne.jp/koichik/
Seasarプロジェクト
Seasar2, Teeda, Dolteng, Kuina-Dao, S2Hibernate,S2Axis, S2RMI, S2Remoting, S2JMS, S2JCA,Aptina, JUnit CDI Extensions, ...
SSJSとの出会い
'96~97年頃実案件でSSJS
金融系 (三大証券)
NetScape Enterprise Server上のLiveWire
Microsoft IIS上のASP (JScript) Microsoft IIS上のASP (JScript)
仕事ではJavaより先にSSJS
でもその後は公私ともずっとJava
Nodeとの出会い
'10年8月ひがさんとの雑談で
@higayasuo もうスマホもPCもクライアントは全部JSでいいよ。
@koichik じゃあサーバもJSで。 @koichik じゃあサーバもJSで。SSJSは昔からあって出てきては消えてるから今も何かあるかも。
ごそごそ (ぐーぐる中)
@koichik 今はNode.jsってのが来てるらしい。あれ?これ今までのSSJSと違う・・・こ、これはっ!?
その後
'10年8月 Node.js日本ユーザグループ発足 APIドキュメントの翻訳に参加
'10年9月ブログにNodeのことを書く Vows async.js async.js
'10年11月 Node本の執筆に誘われる '10年12月 nodejs-devにパッチを送る スルーされる。その後も連続でスルーされる
'10年2月パッチが採用される AUTHORSに記載! @masuidriveに続いて日本人二人目?