SlideShare une entreprise Scribd logo
1  sur  55
Télécharger pour lire hors ligne
東京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「非同期プログラミングの改善」のエッセンス

Contenu connexe

Plus de 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
 

Plus de 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「非同期プログラミングの改善」のエッセンス