2015年11月21日土曜日

learnyounodeを解いてみる(6問目)

learnyounode [MAKE IT MODULAR]


指定したディレクトリから、拡張子でフィルタしたファイルのリストを出力するプログラムを書いてください。 コマンドライン引数の1つ目はディレクトリ名、2つ目は拡張子です。 フィルタリングしたファイル名を1行ずつコンソールに出力してください。非同期 I/O を使ってください。 

module ファイルに処理の大部分を書いてください。module には、3つの引数を取る関数を一つだけ定義してください。 その関数の引数は ディレクトリ名・ファイル拡張子・コールバック関数、という順序です。


次は意外に難問です。learnyounode一の難問と言っていいかもしれません。
問題としては先回のものをそのままモジュールにするだけですが、モジュールにするにはNodeのモジュール化とコールバック関数の使い方にある程度慣れなければなりません。
今までrequire関数で呼び出しのみ使っていたモジュールを自作するのです。

モジュール化自体はいたって簡単です。
exportsにプロパティやメソッドを代入すればいいだけです。

calc.js

exports.add = function(n1, n2) {
    return n1 + n2;
};

そして使用する側はファイル名を指定して呼び出します。

main.js

var calc = require('./calc');

console.log(calc.add(1, 2));

$ node main.js
3


簡単です。ファイル名は拡張子あり'./calc.js'でも無し'./calc'でも行けますがローカルモジュールの場合は先頭にパス'./'が必要です。

ただし、上の書き方だとオブジェクトを代入するとエラーが出ます。Nodeでオブジェクトを代入するにはmodule.exportsを使います。module.exportsは上位互換だと思ってください。なのでNodeでモジュール化するならmodule.exportsを使っておけばいいです。

Modules

先ほどのモジュールをオブジェクトで代入するとこうなります。

calc.js

module.exports = {
    add: function(n1, n2) {
      return n1 + n2;
    }
};


こちらを使っても同じ結果になるはずです。JavaScriptのオブジェクトを知っていればそれほど問題ないでしょう。
以下のようにすればメソッドをどんどん追加できます。

calc.js

module.exports = {
    add: function(n1, n2) {
      return n1 + n2;
    },
    sub: function(n1, n2) {
      return n1 - n2;
    },
    mul: function(n1, n2) {
      return n1 * n2;
    },
    div: function(n1, n2) {
      return n1 / n2;
    }
};


main.js

var calc = require('./calc');

console.log(calc.add(5, 3));
console.log(calc.sub(5, 3));
console.log(calc.mul(5, 3));
console.log(calc.div(5, 3));


$ node main.js
8
2
15
1.6666666666666667


今回の問題にはいろいろ制約があります。

作成する module には、以下の4つの制約があります。
  • 正しい引数を取る関数を定義してください。
  • エラー、もしくはデータを引数に取るコールバック関数を一度だけ呼び出してください。 
  • 2つ目の制約以外には何も変えないでください(グローバル変数や標準出力など)。
  • 発生する可能性のあるエラーは全てコールバック関数に渡してください。 

関数の引数がディレクトリ名・ファイル拡張子・コールバック関数という指定なので先回作ったものをそのまま引数3つの関数でラップしてみます。

findfile.js


var fs = require('fs');
var path = require('path');

var dir = process.argv[2];
var ext = process.argv[3];

/*
  ディレクトリ名・ファイル拡張子・コールバック関数の3つの引数を定義する。
  とりあえずコールバックは定義のみ
 */
var find = function(dir, ext, callback) {   
  //先回作った部分
    fs.readdir(dir, function(err, list) {
     if(err) throw err;
     for(var i in list) {
       if(path.extname(list[i]) == '.' + ext) {
         console.log(list[i]);
       }
     }
   });
}

//作った関数の実行
find(dir, ext, function() {});


$ node findfile.js . txt
test.txt
words.txt


ちゃんと動作しますね。これを出力でなくリストの返り値として変更してみます。問題は得られたリストをどうやって返すかですがここでコールバック関数を使用します。与えられたコールバック関数の引数に渡せば呼び出し先で結果が得られます。

findfile.js

var fs = require('fs');
var path = require('path');

var dir = process.argv[2];
var ext = process.argv[3];

/*
  ディレクトリ名・ファイル拡張子・コールバック関数の3つの引数を定義する。
 */
var find = function(dir, ext, callback) {
    fs.readdir(dir, function(err, list) {
      var files = [];
      if(err) throw err;
      for(var i in list) {
       if(path.extname(list[i]) == '.' + ext) {
         files.push(list[i]);
       }
      }
      //ここで得られた結果を引数として渡す。
      callback(null, files);
  });
}

//コールバック関数から取り出します。
find(dir, ext, function(err, list) {
    console.log(list);
});

これを実行すれば配列ですが先ほどと同じ結果が得られるはずです。
ここまで来れば簡単ですね。あとはエラー内容を指定どおりに処理して、モジュール化し使う側で得られた結果をイテレートするだけです。

findfile.js

var fs = require('fs');
var path = require('path');

module.exports = function(dir, ext, callback) {
    fs.readdir(dir, function(err, list) {
      var files = [];
      if(err) return callback(err);
      for(var i in list) {
        if(path.extname(list[i]) == '.' + ext) {
          files.push(list[i]);
        }
      }
      callback(null, files);
    });
}


usefindfile.js

var findfile = require('./findfile');
var dir = process.argv[2];
var ext = process.argv[3];

findfile(dir, ext, function(err, list) {
    if(err) throw err;
    list.forEach(function(file) {
      console.log(file);
    });
});




$ learnyounode verify usefindfile.js 

・・・・

# おめでとう!

「モジュラーにしましょう」に対するあなたの回答は合格です!

これが正式な回答です(もしあなたの回答と比較してみたいならどうぞ):

───────────────────────────────────────────
solution.js:

    var filterFn = require('./solution_filter.js')
    var dir = process.argv[2]
    var filterStr = process.argv[3]
    
    filterFn(dir, filterStr, function (err, list) {
      if (err)
        return console.error('There was an error:', err)
    
      list.forEach(function (file) {
        console.log(file)
      })
    })

───────────────────────────────────────────
solution_filter.js

    var fs = require('fs')
    var path = require('path')
    
    module.exports = function (dir, filterStr, callback) {
    
      fs.readdir(dir, function (err, list) {
        if (err)
          return callback(err)
    
        list = list.filter(function (file) {
          return path.extname(file) === '.' + filterStr
        })
    
        callback(null, list)
      })
    }

0 件のコメント:

コメントを投稿