SlideShare a Scribd company logo
1 of 55
Download to read offline
東京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に続いて日本人二人目?
東京Node学園#1「非同期プログラミングの改善」のエッセンス

More Related Content

More from koichik

An Introduction to Guarded Horn Clauses
An Introduction to Guarded Horn ClausesAn Introduction to Guarded Horn Clauses
An Introduction to Guarded Horn Clauseskoichik
 
東京Node学園#8 Let It Crash!?
東京Node学園#8 Let It Crash!?東京Node学園#8 Let It Crash!?
東京Node学園#8 Let It Crash!?koichik
 
東京Node学園#6 文字コードと Node
東京Node学園#6 文字コードと Node東京Node学園#6 文字コードと Node
東京Node学園#6 文字コードと Nodekoichik
 
東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolates東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolateskoichik
 
node-handlersocket
node-handlersocketnode-handlersocket
node-handlersocketkoichik
 
2008/02 STMの紹介
2008/02 STMの紹介2008/02 STMの紹介
2008/02 STMの紹介koichik
 
2007/02 ClearCase & UCM の紹介
2007/02 ClearCase & UCM の紹介2007/02 ClearCase & UCM の紹介
2007/02 ClearCase & UCM の紹介koichik
 

More from koichik (7)

An Introduction to Guarded Horn Clauses
An Introduction to Guarded Horn ClausesAn Introduction to Guarded Horn Clauses
An Introduction to Guarded Horn Clauses
 
東京Node学園#8 Let It Crash!?
東京Node学園#8 Let It Crash!?東京Node学園#8 Let It Crash!?
東京Node学園#8 Let It Crash!?
 
東京Node学園#6 文字コードと Node
東京Node学園#6 文字コードと Node東京Node学園#6 文字コードと Node
東京Node学園#6 文字コードと Node
 
東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolates東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolates
 
node-handlersocket
node-handlersocketnode-handlersocket
node-handlersocket
 
2008/02 STMの紹介
2008/02 STMの紹介2008/02 STMの紹介
2008/02 STMの紹介
 
2007/02 ClearCase & UCM の紹介
2007/02 ClearCase & UCM の紹介2007/02 ClearCase & UCM の紹介
2007/02 ClearCase & UCM の紹介
 

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