狠狠撸

狠狠撸Share a Scribd company logo
東京NODE学園 1時限目

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


       小林浩一 @koichik
  http://d.hatena.ne.jp/koichik/
自己紹介

? 後で(ry
Nodeの本を書いてます

? タイトル未定?
? 最初から最後までNode
 ? JSの基本とか他のSSJSとか一切なし
? Node
  Nodeの基本から応用まで盛りだくさん
 ? 500ページ級?
? 発売時期?
 ? 本当はもうすぐ出るはずだったけど???
Node本の構成

? 導入編
? 基本編
? 実践編
? 応用編
Node本の構成

? 導入編
? 基本編
? 実践編
? 応用編
 ? 非同期プログラミングの改善
Node本の構成

? 導入編
? 基本編
? 実践編
? 応用編
 ? 非同期プログラミングの改善
  ? のエッセンス
Agenda

? 非同期プログラミング
 ? 非同期APIのスタイル
 ? 非同期プログラミングの問題
? イディオム
 ? アクターとコールバックの分離
非同期APIのスタイル

? イベントリスナ?スタイル
 ? net, httpモジュール
? コールバック?スタイル
 ? fs, dnsモジュール
イベントリスナ?スタイル

? EventEmitterのサブクラスを使う
? イベントハンドラを登録する

 var server = net.createServer();
 server.on('request', function(socket) {
     ...
 });
 server.on('error', function(err) {
     ...
 });
コールバック?スタイル

? 最後の引数でコールバック関数を渡す
? コールバック関数の最初の引数はエラー

 fs.readFile('foo.txt', function(err, data) {
     ...
 });
幻のプロミス

? コールバックの前はプロミスだった
 var promise = posix.rename("/tmp/hello", "/tmp/world");
 promise.addCallback(function() {
     ...
 }).addErrback(function() {
     ...
 });
? プロミスの発展?応用系が Deferred
 ? Nodeのプロミスはチェーンもできた
 ? しかし品質が低かった
   ? 標準モジュールはもっとシンプルに→コールバック
@masuidriveの悲劇

? 2010/02/13
  ? @masuidrive、プロミスのパッチを送る
? 2010/02/02/17
  ? Node v0.1.29 リリース
  ? パッチが採用される
? 2010/02/22
  ? Node v0.1.30 リリース
  ? プロミス消滅
非同期プログラミングの問題

? イベントリスナスタイルは問題ではない
 ? 理由は書籍で!
? 問題はコールバック?スタイル
コールバック?スタイル

? この同期APIに???
 try {
    data = fs.readFileSync('foo.txt');
    ... // ここで data を扱う
 } catch (err) {
    ... // エラー処理
 }
コールバック?スタイル

? 対応する非同期APIはこう
 fs.readFile('foo.txt', function(err, data) {
     if (err) {
         ... // ここでエラー処理
         (return or throw)
     }
     ... // ここで data を扱う
 });
非同期プログラミングの問題

? 深いネスト
 // 同期                             // こうも書ける
 var x = f();                      var z = h(g(f(x)));
 var y = g(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) {
    ...
 }
非同期プログラミングの問題

? エラーハンドリング (同期)
 try {
    var z = h(g(f(x)));
    ... // 成功時の処理
 } catch (err) {
    ... // エラー処理はここでまとめて
 }
非同期プログラミングの問題

? エラーハンドリング (非同期)
 try {
     f(function(x) {
         g(x, function(y) {
             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');
         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');
          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', ????);
   }
   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 (!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() {
      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');
      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');
  }
}
こうなった


chain(function(next) {
    fs.stat(path, next);
}, function(err, stats, next) {
    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) {
      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) {
       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)
? 仕事ではJavaより先にSSJS
? でもその後は公私ともずっとJava
Nodeとの出会い

? '10年8月ひがさんとの雑談で
 ? @higayasuo もうスマホもPCもクライアントは
   全部JSでいいよ。
 ? @koichik じゃあサーバもJSで。
   SSJSは昔からあって出てきては消えてるから今も
   何かあるかも。
 ? ごそごそ (ぐーぐる中)
 ? @koichik 今はNode.jsってのが来てるらしい。
   あれ? これ今までのSSJSと違う???
   こ、これはっ!?
その後

? '10年8月 Node.js日本ユーザグループ発足
 ? APIドキュメントの翻訳に参加
? '10年9月 ブログにNodeのことを書く
   ? Vows
   ? async.js
? '10年11月 Node本の執筆に誘われる
? '10年12月 nodejs-devにパッチを送る
 ? スルーされる。その後も連続でスルーされる
? '10年2月 パッチが採用される
 ? AUTHORSに記載!
 ? @masuidriveに続いて日本人二人目?
东京狈辞诲别学园#1「非同期プログラミングの改善」のエッセンス

More Related Content

东京狈辞诲别学园#1「非同期プログラミングの改善」のエッセンス