Transcript
Page 1: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

東京NODE学園 1時限目

「非同期プログラミングの改善」のエッセンス

小林浩一 @koichikhttp://d.hatena.ne.jp/koichik/

Page 2: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

自己紹介

後で(ry

Page 3: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Nodeの本を書いてます

タイトル未定?

最初から最後までNode

JSの基本とか他のSSJSとか一切なし

Node Nodeの基本から応用まで盛りだくさん

500ページ級?

発売時期?

本当はもうすぐ出るはずだったけど・・・

Page 4: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Node本の構成

導入編

基本編

実践編

応用編

Page 5: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Node本の構成

導入編

基本編

実践編

応用編

非同期プログラミングの改善

Page 6: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Node本の構成

導入編

基本編

実践編

応用編

非同期プログラミングの改善

のエッセンス

Page 7: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Agenda

非同期プログラミング

非同期APIのスタイル

非同期プログラミングの問題

イディオム イディオム

アクターとコールバックの分離

Page 8: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期APIのスタイル

イベントリスナ・スタイル

net, httpモジュール

コールバック・スタイル

fs, dnsモジュール fs, dnsモジュール

Page 9: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

イベントリスナ・スタイル

EventEmitterのサブクラスを使う

イベントハンドラを登録する

var server = net.createServer();var server = net.createServer();server.on('request', function(socket) {

...});server.on('error', function(err) {

...});

Page 10: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

コールバック・スタイル

最後の引数でコールバック関数を渡す

コールバック関数の最初の引数はエラー

fs.readFile('foo.txt', function(err, data) {fs.readFile('foo.txt', function(err, data) {...

});

Page 11: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

幻のプロミス

コールバックの前はプロミスだった

var promise = posix.rename("/tmp/hello", "/tmp/world");promise.addCallback(function() {

...}).addErrback(function() {

プロミスの発展・応用系が Deferred Nodeのプロミスはチェーンもできた

しかし品質が低かった 標準モジュールはもっとシンプルに→コールバック

}).addErrback(function() {...

});

Page 12: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

@masuidriveの悲劇

2010/02/13

@masuidrive、プロミスのパッチを送る

2010/02/02/17

Node v0.1.29 リリース Node v0.1.29 リリース

パッチが採用される

2010/02/22

Node v0.1.30 リリース

プロミス消滅

Page 13: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

イベントリスナスタイルは問題ではない

理由は書籍で!

問題はコールバック・スタイル

Page 14: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

コールバック・スタイル

この同期APIに・・・try {

data = fs.readFileSync('foo.txt');... // ここで data を扱う

} catch (err) {} catch (err) {... // エラー処理

}

Page 15: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

コールバック・スタイル

対応する非同期APIはこう

fs.readFile('foo.txt', function(err, data) {if (err) {

... // ここでエラー処理... // ここでエラー処理(return or throw)

}... // ここで data を扱う

});

Page 16: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

深いネスト// 同期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) {

...});

});});

Page 17: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

無名関数をやめても・・・f(gotX);function gotX(x) {

g(x, gotY);} 名前でジャンプするなんてgoto文でしょ}function gotY(y) {

h(y, gotZ);}function gotZ(z) {

...}

名前でジャンプするなんてgoto文でしょ

Page 18: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

エラーハンドリング (同期)

try {var z = h(g(f(x)));... // 成功時の処理

} catch (err) {} catch (err) {... // エラー処理はここでまとめて

}

Page 19: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

エラーハンドリング (非同期)try {

f(function(x) {g(x, function(y) {

h(y, function(z) {h(y, function(z) {... // 成功時の処理

});});

});} catch (err) {... // エラー処理?

}間違い!

Page 20: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

エラーハンドリング (非同期)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;

}... // 成功時の処理

});});

});

Page 21: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングの問題

無名関数を使うとネストが深くなる

無名関数を使わなければgotoもどきになる

エラー処理が散在する

Page 22: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Agenda

非同期プログラミング

非同期API

非同期プログラミングの問題

イディオム イディオム

アクターとコールバックの分離

Page 23: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

同期版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);

}}

Page 24: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期版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');

});});

});}

Page 25: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

インラインの無名関数をやめる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');

}}

Page 26: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

やりたいことは何か?

ネストを深くしたくない

コールバックをインライン (無名関数) にしなければよい

ラベル (関数名) に頼りたくない

コールバックを無名関数にすればよい

Page 27: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

やりたいことは何か?

ネストを深くしたくない

コールバックをインライン (無名関数) にしなければよい

ラベル (関数名) に頼りたくない

コールバックを無名関数にすればよい

矛盾!

Page 28: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非インラインの無名関数にしてみる

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');

}}

Page 29: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

何が起きたか?

無名関数を非同期APIのコールバックとして渡せなくなった

コールバックはどうする?何を渡す?

Page 30: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

そこで!

無名関数とコールバックを分離する

コールバックの役割

「次」の無名関数を呼び出す

無名関数の役割

アプリケーション固有の処理

非同期APIを呼び出す

これを「アクター」と呼ぶ

Erlang他のアクターとは無関係

Page 31: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

アクターとコールバックを結びつける

誰が?

ライブラリ

フロー制御モジュールと呼ばれる

複数のアクターを受け取る 複数のアクターを受け取る

配列 or 可変長引数 (arguments)

アクターにコールバックを提供する

コールバックはアクターを順次呼び出す

Page 32: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

フロー制御モジュールのイメージ

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');

});

Page 33: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

フロー制御モジュールの実装

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);

}}

Page 34: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

結果

たった12行の関数で

Page 35: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

これや

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');

});});

});}

Page 36: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

これが

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');

}}

Page 37: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

こうなった

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');

});

Page 38: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

効果

ネストは深くならない

無駄な名前に頼らない

Page 39: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

課題

エラーハンドリングは?

Page 40: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

エラー時のルーティング

アクターごとにエラー処理をしたくない

エラーが起きたら途中のアクターをすっ飛ばそう

最後のアクターでまとめてエラー処理 最後のアクターでまとめてエラー処理

try~catch と同じ

Page 41: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

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);

}}

Page 42: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

結果

たった13行の関数で

Page 43: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

こうなった

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');

});}

Page 44: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

課題

無名関数大杉ね?

一行ばっかだし

Page 45: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

続きは書籍で!

別のイディオムも!

すぐに使えるフロー制御モジュールの紹介も!

Page 46: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

まとめ

コールバックスパゲッティ?

ちゃんちゃらおかしいね

いくらでも工夫できる!

自分のフロー制御モジュールを作ってみよう! 自分のフロー制御モジュールを作ってみよう!

世界のデファクトになるかもよ!?

先人達の工夫 https://github.com/joyent/node/wiki/modules#async-flow

CSJSや他言語のアイディアも活用しよう

Deferred, ReactiveExtension, ファイバ, ...

Page 47: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

非同期プログラミングは怖くないよ怖くないよ

Page 48: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Q&A

ご質問があればどうぞ!

Page 49: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

ご清聴ありがとうございました

Page 50: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

こんなこともあろうかと

Page 51: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

自己紹介

小林浩一

@koichik

http://d.hatena.ne.jp/koichik/

Seasarプロジェクト

Seasar2, Teeda, Dolteng, Kuina-Dao, S2Hibernate,S2Axis, S2RMI, S2Remoting, S2JMS, S2JCA,Aptina, JUnit CDI Extensions, ...

Page 52: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

SSJSとの出会い

'96~97年頃実案件でSSJS

金融系 (三大証券)

NetScape Enterprise Server上のLiveWire

Microsoft IIS上のASP (JScript) Microsoft IIS上のASP (JScript)

仕事ではJavaより先にSSJS

でもその後は公私ともずっとJava

Page 53: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Nodeとの出会い

'10年8月ひがさんとの雑談で

@higayasuo もうスマホもPCもクライアントは全部JSでいいよ。

@koichik じゃあサーバもJSで。 @koichik じゃあサーバもJSで。SSJSは昔からあって出てきては消えてるから今も何かあるかも。

ごそごそ (ぐーぐる中)

@koichik 今はNode.jsってのが来てるらしい。あれ?これ今までのSSJSと違う・・・こ、これはっ!?

Page 54: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

その後

'10年8月 Node.js日本ユーザグループ発足 APIドキュメントの翻訳に参加

'10年9月ブログにNodeのことを書く Vows async.js async.js

'10年11月 Node本の執筆に誘われる '10年12月 nodejs-devにパッチを送る スルーされる。その後も連続でスルーされる

'10年2月パッチが採用される AUTHORSに記載! @masuidriveに続いて日本人二人目?

Page 55: 東京Node学園#1「非同期プログラミングの改善」のエッセンス

Recommended