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

Preview:

Citation preview

東京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に続いて日本人二人目?

Recommended