Contenu connexe Similaire à Node-v0.12の新機能について Similaire à Node-v0.12の新機能について (20) Plus de shigeki_ohtsu (19) Node-v0.12の新機能について3. 本日の内容(*)
1. Node-v0.12の概要
2. ES6 /7対応 (Promise, Object.observe, WeakMap/WeakSet)
3. Streams3 (Stream1+2の復習も兼ねて)
4. 同期child_process, beforeExitイベント(process)
5. 新 vm モジュール (時間があれば)
6. tracing モジュール (時間があれば)
7. Clusterのラウンドロビンモード(時間があれば)
8. 高速化の話(時間があれば)
全体的な性能向上も大きな売り。ベンチ結果は、おそらく
リリース時に公式な数値が出るので今回は出しません。
(* 2014/04/23時点での master branch が対象)
4. Nodeのこれまでの歩み
2007/10 libev公開
2008/05 libeio公開
2008/09 Google V8公開
2009/02 ryan dahl Node開発開始
2009/05 node-v0.0.1 リリース
2009/06 nodejs ML開始
2009/10 npm公開
2009/11 JSConf EU ry発表
2010/04 Herokuサポート
2010/08 node-v0.2 リリース
2010/11 Joyent管轄へ
2011/02 node-v0.4 リリース
2011/03 libuv 開発開始
2011/11 node-v0.6 リリース
2011/12 Windows Azureサポート
2012/01 管理者が isaacsに変更
2012/06 node-v0.8 リリース
2013/03 node-v0.10 リリース
2014/01 管理者が TJ Fontaineに変更
? node-v0.12 リリース
5. Node-v0.12概要
• Node-v0.12は、Node-v1.0の Release Candidate
の位置付け。 まだリリースされていません!
• Node-v0.10から安定性と性能向上を中心に、派
手な新機能の追加や大幅なAPI廃止・変更はあ
りません(ただし仕様や挙動変更の部分は多々あります)。
• コアの中身は大幅に変更されてます。
• V8も大きくバージョンアップ 3.14→3.25 。それに
伴い V8 APIが大幅に変更→以前のNativeモ
ジュールは大きく書き換えが必要。
• libuv もいろいろ細かく変わっていますが、0.10ほ
どではないので…
6. Node の ES6/ES7対応
• Chrome M35 が何かと機能がてんこ盛り。
• そこに載っているV8 3.25 で ES6/ES7 の一部機能
が先行的に default で有効に(*)
• Node も V8 3.25 にアップデート
• Promise/Object.observe/WeakMap/WeakSet が
harmony オプションなしで利用が可能になった。
• 全部詳細説明すると時間が足りないので簡単に。
(* なぜかV8 3.26では要オプションに戻されてます)
8. Promise
• Promise A+仕様をベースにES6で採用
• 非同期処理(成功時:OnFulfill, エラー時:
OnRejected)を登録、実行
• then()やcatch()でメソッドチェーンの利用
• then() を持ったオブジェクト(thenable)からプ
ロミスオブジェクトを生成
• Promise.all([p1, p2, …]) で全てのプロミスオブ
ジェクトの成功を一括処理
9. Promies利用例
引数で指定したファイルの確認を Promise で処理するサンプル
var fs = require('fs');
var file = process.argv[2] || __filename;
var promise = new Promise(function(onFulfilled, onRejected) {
fs.stat(file, function(err, stat) {
// return stat object when fulfilled
err ? onRejected(err) : onFulfilled(stat);
});
});
// 正常処理なら onFulfilled, エラーなら onRejected が実行される
promise.then(
function(stat) {
console.log('Fulfilled:', file, stat);
},
function(error) {
console.log("Rejected:", error.message);
}
);
10. Promiseの利用例
こんなコールバック地獄が
fs.readdir(dir1, function(...) { // dir1中のファイルリストを 取得
fs.readFile(file1, function(...) { // その中のfile1を 読み込み
hash.update(data); // ファイルデータのハッシュ値を計算
fs.readdir(dir2, function(...) { // dir2中のファイルリストを 取得
fs.readFile(file2, function(...) {
...
});
});
});
});
dir1 と dir2 のディレクトリ中のファイルハッシュの比較
11. Promiseの利用例
こんな風に変えられます
function compareDirHash(dir1, dir2) {
return compareFilesPromise(dir1, dir2).then(function(files) {
return Promise.all(files.map(function(file) {
return compareHashPromise(dir1, dir2, file);
}));
}).then(
function(x) {
// 正常処理
},
function(err) { // all case of errors
// エラー処理
}
);
}
compareDirHash('./dir1/', './dir2/');
dir1 と dir2 のディレクトリ中のファイルハッシュの比較
Array.mapで処理結果を
配列にしてから
Promise.all() を使うと便利
全部正常に
完了した時の
処理がまとめ
て書ける
エラー処理も
扱いやすい
13. Object.observe 実行例
検知できるイベント(*)
Object.observe Array.observe
add add
update update
delete delete
setPrototype splice
reconfigure
preventExtension
var obj = {};
function callback(changeRecord) {
console.log(changeRecord);
}
Object.observe(obj, callback);
obj.a = 1; // プロパティ追加
$ node test.js
[ { type: 'add', object: { a: 1 }, name: 'a' } ]
実行例
(* Notifierを使えば独自イベントも追加可能)
15. WeakMap/WeakSet
• オブジェクトのみをキーにしたハッシュテーブル。
• WeakMapは、キーと値を WeakSet はキーのみ登録
• 弱参照なのでキーがGCされるとエントリーが自動的
に削除される。
• for とかで内部のエントリーを列挙したり、格納されて
いるエントリー数を取得するようなことはできない。
• soft field と object cache という目的で仕様化された
• ブラウザでは、DOMのプロパティを勝手に拡張して
フィールドを付け加えるのではなく、WeakMapを使う。
• (何故か?) IE11 で WeakMap が実装済
16. WeakMap利用例 (*)
WeakMapを使った Private変数の隠匿
var weakmap = new WeakMap();
module.exports = Hoge;
function Hoge(size) {
size = +size || 8;
var rand = require('crypto').randomBytes(size);
// thisをキーとしてプライベート変数をWeakMapに格納
weakmap.set(this, rand);
};
Hoge.prototype.getRand = function() {
// WeakMapからプライベート変数を取り出す
return weakmap.get(this);
};
(* 別にNode固有の使い方じゃありません)
インスタンス
がGCされれば
勝手になくな
るよ
20. Stream1 の問題
var server = http.createServer(function(req, res) {
setTimeout(function() {
var data = '';
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
console.log(data);
});
res.writeHead(200);
res.end();
}, 1000);
}).listen(8080);
POSTデータをちゃんと取れる?
補習資料
28. Streams3 モードの行き来(その1)
paused → flowing → paused (pauseの利用)
var rstream = require('fs').createReadStream(__filename);
rstream.on('readable', function() {
while((b = rstream.read(1)) !== null) {
console.log('read:', b.toString());
}
});
var bytesRead = 0;
rstream.on(‘data’, function(chunk) { // flowing mode へ
bytesRead += chunk.length;
console.log(bytesRead + ' bytes read');
});
rstream.pause(); // paused modeへ
読み込み進捗率を表示
1バイトずつ表示
readableイベントと dataイベントの両立が可能になった
29. Streams3 モードの行き来(その2)
paused → flowing (resumeの利用)
var rstream = require('fs').createReadStream(__filename);
rstream.resume(); // flowing mode へ
// data listener がないのでデータが失われてるよ!
rstream.on('readable', function() {
console.log(rstream.read()); // 出力なし
});
rstream.on('end', function() {
console.log('stream end');
});
flowing mode
では readできな
いよ
内部バッファを消
費しないと end
は発火しない
30. Streams3 モードの行き来(その3)
paused → flowing (pipeの利用)
var rstream = require('fs').createReadStream(__filename);
rstream.on('readable', function() {
var b;
while((b = rstream.read(1)) !== null) {
console.log('read:', b.toString());
}
});
rstream.pipe(process.stdout); // flowing mode へ
その1の pipe版
31. Streams3 モードの行き来(その4)
paused → flowing → pause (dataイベント削除+unpipeの利用)
var rstream = require('fs').createReadStream(__filename);
rstream.on('readable', function() {
var b;
while((b = rstream.read(1)) !== null) {
console.log('read:', b.toString());
}
});
rstream.pipe(process.stdout); // flowing mode へ
rstream.removeAllListeners(‘data’).unpipe(); // paused modeへ
dataイベントを全て除いて pipe も外
したらやっと paused modeに変遷
32. その他 Stream の変更
Writable Stream のAPI拡張
• writable.cork() 書き込みを停止して溜める
• writable.uncork() 溜めるのやめる
• writable._writev(chunks, callback) 一気に書き
出す
34. execSync使用例
Node Shell
var execSync = require('child_process').execSync;
var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('Node_Shell$ ');
rl.prompt();
rl.on('line', function(cmd) {
try {
var ret = execSync(cmd.trim()); // 同期処理
console.log(ret + '');
} catch(e) {
console.err(e.code);
}
rl.prompt();
});
35. beforeExitイベント新設
• process の exit オブジェクトは、JavaScriptの実
行はできるけど、イベントループが終了してい
るため、もうI/O処理はできない状態。
• プロセス終了間際に、ゴミ掃除やログの送信、
他へのhookなどI/O処理をしたい。
• プロセス終了直前に、もう一度イベントループ
を回してJavaScript実行できる beforeExit イベ
ントを process オブジェクトに新設。
36. beforeExit サンプル
httpクライアントの一時ファイル掃除
var fs = require(‘fs’), http = require('http');
var tmpdir = './tmpdir/', hosts = ['localhost'];
if (!fs.existsSync(tmpdir)) fs.mkdirSync(tmpdir);
hosts.forEach(function(host) {
http.get({hostname: host}).on('response', function(res) {
var tmpf = tmpdir + host;
res.pipe(fs.createWriteStream(tmpf)).on('finish', function() {
console.log('Do something special to the tmp file.');
});
});
});
// Cleanup temporary files
process.once('beforeExit', function() {
fs.readdir(tmpdir, function(err, files) {
files.forEach(function(file) {
fs.unlink(tmpdir + file);
});
});
});
プロセスが終了する前に一時ファイルを
全部削除する。
リスナ登録を once にしておかないと、再
帰で呼び出されるから注意です。
httpクライアントによる
コンテンツの取得と一
時ファイルへの書き込
み全部終わったら、イ
ベントループが終了。
出ていく前に
掃除しろよー
37. 新vmモジュール
• vmモジュールとは、JavaScriptを実行するモジュール。
• Context やsandboxを指定でき、iframe 上のJavaScript実行
のようなもの(でもセキュリティ的に完全分離してない)。
• 信頼できない外部のJSを実行・評価するような時に使う。(使
用例:UglifyJS2とか)
• Node-v0.10までは色々問題があった。
Node-v0.10.26 vmモジュールマニュアルより
vm に渡された sandbox オブジェクトを global に単純に clone し
てたため次頁のような issue が残っていた。
38. 旧vmモジュールのダメな例
var vm = require('vm');
var sandbox = Object.create({foo: 'hoge'});
var ret = vm.runInNewContext('foo;', sandbox);
console.log(ret); // Reference Error
var vm = require('vm');
var sandbox = {foo: 'hoge'};
var ret = vm.runInNewContext('this;', sandbox);
console.log(ret); // {} が返る
var vm = require('vm');
var code = "setTimeout(function(){foo = 1}, 2000);";
var sandbox = {foo: 'hoge', setTimeout: setTimeout};
var ret = vm.runInNewContext(code, sandbox);
setInterval(function() {
console.log(sandbox.foo); // 2秒後以降もhoge
}, 500);
prototypeが継承されない
this が {} オブジェクトに
非同期の変更に追従しない
39. 新vmモジュール
• node_contextfy モジュールをベースにコアに
取り込み。
• 完全に V8::Context を分離。
• 先の不具合を解消。
• WatchDogを導入、timeout 指定も可能に。
var vm = require('vm');
var opts = {timeout: 100};
try {
var ret = vm.runInNewContext('while(true){}', {}, opts);
} catch(e) {
console.log(e);
}
42. Cluster Modeの比較・検証する
var cluster = require('cluster'), http = require('http');
if (process.argv[2] === 'none') cluster.schedulingPolicy = cluster.SCHED_NONE;
var mode = cluster.schedulingPolicy === 1 ? 'None' : 'Round Robin';
var N = 4, results = {}, total = 0;
cluster.isMaster ? Master() : Worker();
process.on('result', function() {
for(var pid in results) {
console.log('pid:', pid, 'requests:' ,results[pid]);
}
});
function Master() {
console.log('cluster.schedulingPolicy:', mode);
for(var i = 0; i < N; i++) {
cluster.fork().on('message', function(msg) {
results[this.process.pid + ''] = msg;
if (++total%1000 === 0) process.emit('result');
});
}
}
function Worker() {
var counter = 0;
var server = http.createServer(function(req, res) {
res.writeHead(200, {'content-type': 'text/plain'});
res.end('Hello World');
process.send(++counter);
}).listen(8080, function() {
console.log('Listening on pid:' + process.pid);
});
43. 結果
ab -n 1000 http://localhost:8080/
cluster.SCHED_NONE
$ node test.js none
cluster.schedulingPolicy: None
Listening on pid:1831
Listening on pid:1834
Listening on pid:1832
Listening on pid:1836
pid: 1831 requests: 211
pid: 1832 requests: 267
pid: 1834 requests: 260
pid: 1836 requests: 262
cluster.SCHED_RR
$ node test.js
cluster.schedulingPolicy: Round
Robin
Listening on pid:1819
Listening on pid:1822
Listening on pid:1820
Listening on pid:1824
pid: 1819 requests: 250
pid: 1820 requests: 250
pid: 1822 requests: 250
pid: 1824 requests: 250
(環境: Ubuntu14.04, kernel-3.13)