ユーザ用ツール

サイト用ツール


nodejs

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
nodejs [2015/11/06 05:54] – [callbackと例外のアンチパターン] nullponnodejs [2016/10/13 02:30] (現在) – [--max-old-space-size] nullpon
行 2: 行 2:
 [[http://nodejs.org/|node.js]]はノンブロッキングI/Oとイベント駆動が特徴のJavaScript実行環境。イベントループ型のサーバを簡単に実装できる。Chromeブラウザと同じV8エンジンが使用されている。 [[http://nodejs.org/|node.js]]はノンブロッキングI/Oとイベント駆動が特徴のJavaScript実行環境。イベントループ型のサーバを簡単に実装できる。Chromeブラウザと同じV8エンジンが使用されている。
  
-===== tips =====+===== バージョンとES feature =====
  
-==== callbackと例外アンチパタン ====+ブラウザと違って互換性を気にする必要はないので最新機能は遠慮なく使いましょう 
 + 
 +node v4 
 + 
 +  * デフォルト引数、分割代入、import以外のES2015機能が使えます。 
 + 
 +node v6 
 + 
 +  * import以外のES2015機能が使えます。 
 + 
 +===== callbackに関するtips ===== 
 + 
 +==== callbackの第一引数はerrにする ==== 
 + 
 +絶対にエラーの発生しない処理だしてもcallback第一引数はエラが渡される想定のコードにすること。第一引数にnullを渡してcallbackを呼べし。 
 + 
 +node, npmの多くのライブラリは第一引数でエラーを受け取る事を前提にしているので、こうすることで各種ライブラリ、モジュールとスムーズに連携できるようになる。 
 + 
 +<code javascript> 
 +// これはだめ 
 +function asyncFunc(callback) { 
 +   setImmediate(function() { 
 +      callback(true); 
 +   }); 
 +
 +</code> 
 + 
 +<code javascript> 
 +// OK 
 +function asyncFunc(callback) { 
 +   setImmediate(function() { 
 +      callback(null, true); 
 +   }); 
 +
 +</code> 
 + 
 + 
 +==== 非同期処理には必ずcallbackを ==== 
 + 
 +呼び出し元に結果を通知する必要のない非同期処理でも必ずcallbackを付ける(PromiseかEvent駆動ならcallbackなし可) 
 + 
 +bad  
 + 
 +<code javascript> 
 +function writeLogToDB(code, content) { 
 +   // 失敗しても良い処理なので、呼び出しもとにエラーを通知しなくてもいい (← ダメです。だいたい後で通知したくなります) 
 +   db.Log.insert({_id: code, content: content}, function(err) { 
 +      err && mainLogger.error(err); 
 +   }); 
 +
 +</code> 
 + 
 +good 
 + 
 +<code javascript> 
 +function writeLogToDB(code, content, callback) { 
 +   // callbackが渡されてなければデフォルトの処理を入れる 
 +   if (!callback) callback = function(err) { 
 +      err && mainLogger.error(err);    
 +   } 
 +    
 +   db.Log.insert({_id: code, content: content}, function(err) { 
 +      callback(err); 
 +   }); 
 +
 +</code> 
 + 
 +==== callbackを2度呼ばない ====
  
 <code> <code>
行 18: 行 85:
 </code> </code>
  
-try側のcallback中に例外が発生するとcatch側のcallbackが呼ばれるので2回呼ばれてしまう+try側のcallback中に例外が発生するとcatch側のcallbackが呼ばれるので2回呼ばれてしまう。これによってデータ2重書き込み等の重大な問題が発生する恐れがあります。
  
  
-==== requireのアンチパターン ====+===== require =====
  
-requireされるファイルは関数やクラスの定義のみにする。読み込んだだけでファイルの書き込み読み込み、サーバの起動、DB接続その他の処理を行うjsだめ+==== require時に処理を走らせない ==== 
 + 
 +requireされるファイルは関数やクラスの定義のみにする。読み込んだだけでファイルの書き込み読み込み、サーバの起動、DB接続その他の処理を行っていけない
  
 Good Good
-<code>+<code javascript>
 // hoge.js // hoge.js
 var hoge = module.exports; var hoge = module.exports;
行 34: 行 103:
 } }
  
-// main.js 
  
-var hoge = require("./hoge"+// main.js 
-hoge.init();+var hoge = require("./hoge"); 
 +hoge.init();              // 初期化処理の実行はrequireしたファイルで行う
 </code> </code>
  
 Bad Bad
  
-<code>+<code javascript>
 // hoge.js // hoge.js
 var hoge = module.exports; var hoge = module.exports;
行 50: 行 119:
 } }
  
-hoge.init();  // requireしただけでinitされる+hoge.init();  
  
-// main.js 
  
-require("./hoge")+// main.js 
 +require("./hoge");        // requireしただけでinitされてしまう
 </code> </code>
  
 requireされるだけで何らかの処理が走るようにすると、起動時に予期せぬタイミングでinitが実行されたり、テストコードでinitが不要なのに呼ばれてしまったりする問題が発生します。 requireされるだけで何らかの処理が走るようにすると、起動時に予期せぬタイミングでinitが実行されたり、テストコードでinitが不要なのに呼ばれてしまったりする問題が発生します。
  
 +===== メモリ =====
  
 +==== --max-old-space-size ====
 +<markdown>
 +デフォルトで1.5GB以上のメモリを確保しようとするとクラッシュします。ひたすらメモリを食いつぶしていくコードを実行すると…
  
 +```
 +<--- Last few GCs --->
  
-===== HTML5 の Server-Sent Events を実装してみる =====+   84313 ms: Mark-sweep 1389.5 (1456.5) -> 1396.1 (1456.5) MB, 844.5 / 0 ms [allocation failure] [GC in old space requested]. 
 +   85167 ms: Mark-sweep 1396.1 (1456.5) -> 1398.0 (1456.5) MB, 854.0 / 0 ms [allocation failure] [GC in old space requested]. 
 +   86028 ms: Mark-sweep 1398.0 (1456.5) -> 1387.1 (1456.5) MB, 860.4 / 0 ms [last resort gc]. 
 +   86898 ms: Mark-sweep 1387.1 (1456.5) -> 1389.0 (1456.5) MB, 870.4 / 0 ms [last resort gc].
  
-[[http://d.hatena.ne.jp/paulownia/20110110/1294671579|node.jsでServer-Sent Eventsを試す - nullpo.printStackTrace();]] 
  
-サーバ側を node.js で実装してみる。+<--- JS stacktrace --->
  
-generator.js +==== JS stack trace =========================================
-<code javascript> +
-// EventEmitterを取得 +
-var events require("events"); +
-var emitter new events.EventEmitter();+
  
-// EventEmitterを拡張 +Security context: 0x2e7afd4b4629 <JS Object> 
-emitter.id 0; +    1: nextTick [node.js:~473] [pc=0x1cfdf0766f8e] (this=0x3bb898d1c211 <a process with map 0x31a25a8113b9>,callback=0x1d8be6ffc341 <JS Function afterWrite (SharedFunctionInfo 0x1d8be6f1c359)>
-emitter.data ""; +    2: arguments adaptor frame: 5->1 
-emitter.start function() { +    3: onwrite(aka onwrite[_stream_writable.js:~314] [pc=0x1cfdf075ee02] (this=0x2e7afd4041b9 <undefined>,stream=0x1d8be6fb5d81 <a WriteStream with map 0xbb33c7202f...
-    var chars = "node.js"; +
-    var self = this; +
-    setInterval(function() +
-        var pos = Math.floor(Math.random() * chars.length * 2);  +
-        if (pos chars.length) { +
-            self.id++; +
-            self.data chars.charAt(pos); +
-            // generatedイベントとして登録されたリスナを呼び出す +
-            self.emit("generated", self.id, self.data);   +
-        } +
-    }, 1000); +
-};+
  
-// module.exportsに設定されたオブジェクトがrequireの戻り値となる。 +FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory 
-module.exports = emitter;  +zsh: abort      node hoge.js 
-</code>+```
  
-server.js +上限を増やすには
-<code javascript> +
-var http = require("http"+
-http.createServer(handler).listen(8124);+
  
-var generator require("./generator") +``` 
-generator.start();+$ node --max-old-space-size=2048 hoge.js 
 +```
  
-function handler(req, res) {+これを実行すると…
  
-    function response(id, data) { +``` 
-        generator.removeListener("generated", arguments.callee); +<--- Last few GCs --->
-         +
-        res.writeHead(200,+
-            "Content-type": "text/event-stream" +
-        }); +
-        res.write("id:" + id + "\n"); +
-        res.write("data:" + data + "\n"); +
-        res.write("retry: 1000"); +
-        res.end(); +
-         +
-    } +
-    generator.on("generated", response); +
-+
-</code>+
  
-クライアント側のJavaScript +  183271 ms: Scavenge 2044.5 (2114.7) -> 2044.5 (2114.7) MB, 0.8 / 0 ms (+ 1.4 ms in 1 steps since last GC) [allocation failure] [incremental marking delaying mark-sweep]. 
-<code javascript+  184563 ms: Mark-sweep 2044.5 (2114.7) -> 2035.4 (2114.7) MB, 1291.3 / 0 ms (+ 2.3 ms in 2 steps since start of marking, biggest step 1.4 ms) [last resort gc]. 
-var source new EventSource(url)+  185914 ms: Mark-sweep 2035.4 (2114.7) -> 2036.5 (2114.7) MB, 1351.8 / 0 ms [last resort gc]. 
-source.onmessage function(event{ + 
-  // データ受信時の処理を書く + 
-  // event.data でサーバから送られてきたデータを取得できる +<--- JS stacktrace ---
-+ 
-</code>+==== JS stack trace ========================================= 
 + 
 +Security context: 0x1f9ab28b4629 <JS Object> 
 +    1: $toString(aka ToString[native runtime.js:~563] [pc=0x3fdefe2641cb] (this=0x1f9ab28919c1 <JS Object>,i=0xecfd57afc99 <Number: 1.95963e+09>
 +    2: a(aka a) [/Users/nullpon/hoge.js:14] [pc=0x3fdefe249ab8] (this=0x1f9ab28041b9 <undefined>) 
 +    3: /* anonymous *[/Users/nullpon/hoge.js:20] [pc=0x3fdefe247d7d] (this=0x3c41316b29c9 <an Object with map 0x24b594a087d1>,exports=0x3c41316b29c9 <a... 
 + 
 +FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory 
 +zsh: abort      node --max-old-space-size=2048 hoge.js 
 +```
  
 +今度は2GBまで持ちました。が、こんなにメモリを使う実装の前に色々直しましょう
 +</markdown>
nodejs.1446789246.txt.gz · 最終更新: 2015/11/06 05:54 by nullpon