機械系大学生の修行ログ

sh-lu0's Tech Blog

アイドルが力をくれる

Node.js学習まとめ

Node.jsとは

  • サーバーサイドのJavaScript
  • ノンブロッキングI/O(大量のアクセスに強い)
  • イベントループ
  • シングルスレッド
  • ChromeのV8エンジンを取り出して強化したJavaScriptの「実行環境」(速い)

ノンブロッキングI/O

ファイルやネットワークの入出力(I/O)を行う際、処理をブロックしない。
I/Oの処理を待たずに次の処理を始めるため、大量のデータ処理などが可能。
- ブロックのないイベントループ
- ノンブロッキングを強制する
- Google の V8エンジンにより実現

例えば、ハードディスクの入出力のスピードは、CPUやメモリーから見ると非常に遅い。書き込み完了を待っているのは非常に時間の無駄なので、ハードディスクに書き込む命令を出して、書き込めたかの結果を待たず次の処理をどんどん進めてしまうので非常に処理を早くすすめられる。書き込めた場合にコールバックが呼ばれる。

イベントドリブン

イベントドリブンとは、 コンピュータプログラムの開発および実行方式の一つで、 利用者や外部の別のプログラムなどが引き起こす出来事に 対応する形で処理を記述あるいは実行する方式。 (wikipediaより)

シングルスレッド

C10K問題 (クライアント1万台接続問題)

  • 大規模なI/Oが発生する処理の需要が高まった
  • Apachなどは1HTTPリクエストに1プロセス(ポート数の32767が限界)
  • マルチスレッドはメモリの消費が激しい

Node.js入門
Node.js を5分で大雑把に理解する - Qiita https://www.slideshare.net/SatoshiTakami/intoroduction-nodejs
内部実装から読み解くNode.js(v11.0.0) Eventloop - Qiita

npm

「npm」とは「Node Package Manager」の略で、Node.jsのパッケージを管理するツール。

カレントディレクトリにインストール

$ npm install hoge

グローバルインストール

$ npm root -g hoge

npmのグローバルインストールとローカルインストール - Qiita

セットアップ

インストール

nodebrewによるNode.jsのインストールからセットアップまで - Qiita

バージョン変更

// 確認
$node --version
v11.14.0
// インストール
$ nodebrew install v12.2.0
// インストール済みのリスト表示
$ nodebrew list
v10.15.3
v11.14.0
v12.2.0

current: v11.14.0
// 使用するバージョンを変更
$ nodebrew use v12.2.0
// 確認
$ node --version
v12.2.0

【Mac】nodebrewのインストールとコマンドなど使い方まとめ - TASK NOTES

NodeSchool

NodeSchool

learnyounode 1問目

Hello World

console.log("HELLO WORLD")

learnyounode 2問目

コマンドライン引数

process には argv という配列のプロパティがあり、
その配列の中にはコマンドライン引数が入っている。

console.log(process.argv)

node program.js testで実行すると
argvの1つ目にnode、2つ目にアプリファイルのパス、
それ以降にコマンドライン引数が入っているのが分かる。

[
  '/Users/username/.nodebrew/node/v12.2.0/bin/node',
  '/Users/username/Projects/learnyounode/02_baby_step.js',
  'test' 
]
Number()

process.argvの要素は全てstringであるため、
「+」を前につけるか、Number()で変換する

result += +process.argv[i]
result += Number(process.argv[i])
解答
var result = 0

for (var i = 2; i < process.argv.length; i++)
  result += Number(process.argv[i])

console.log(result)

learnyounode 3問目

fsモジュール

ファイルシステムにアクセスするためには、
Nodeのコアライブラリのfsモジュールが必要。

var fs = require('fs')
readFileSync

同期処理(ブロッキング)をする関数は、名前の後ろにSyncがついている。 fs.readFileSync('path')でファイルを読み込むと、Bufferオブジェクトが返ってくる。 そのため、toString()で変換する必要がある。

解答
// fsモジュールを使う
var fs = require('fs')

// コマンド引数の配列の中の3番目を読み取り
var contents = fs.readFileSync(process.argv[2])
var lines = contents
    .toString() //バッファーを文字列に変換
    .split('\n') // 改行コードで分割
    .length - 1 // 最終行を除外
console.log(lines)

learnyounode 4問目

readFile()

非同期でファイルを読み込む。 返り値を受け取るのではなく、
2つ目の引数であるコールバック関数を使って値を受け取る。

callback関数
function callback (err, data) { /* ... */ }

エラーが発生しなければerrはnullとなり、
2つ目の引数には Bufferオブジェクトが入る。

readFile(file, 'utf8', function (err, contents)

ァイルパスとコールバック引数の間に "utf8"を入れると、
Bufferのかわりに Stringが返る。

解答
var fs = require('fs')
var file = process.argv[2]

// fileの読み込みが終わったらfunctionを実行
fs.readFile(file, function (err, contents) {
  if (err) {
    return console.log(err)
  }
  var lines = contents.toString().split('\n').length - 1
  console.log(lines)
})

learnyounode 5問目

readdir()

1つ目の引数はディレクトリへのパス。
2つ目の引数はコールバック関数で、
listはファイル名のStringが格納されたArray。

function callback (err, list) { /* ... */ }
path.extname関数

パスから拡張子部分を取得する。

var path = require('path')
var ext = path.extname('xxx');
解答
var fs = require('fs')
var path = require('path')

var folder = process.argv[2]
var ext = process.argv[3] || ""

fs.readdir(folder, function (err, list) {
  if (err) {
    return console.error(err)
  }
  list.forEach(function (file) {
    if (path.extname(file) === '.' + ext)
      console.log(file)
  })
})

learnyounode 6問目

module化

module.exportsで、モジュール化させる関数を「1つだけ」定義する。
呼び出し側で、var module_hoge = require('./module.js')
と定義すると、module_hoge()として使用できるようになる。

解答

make_it_modular.js

var findfile = require('./module.js')
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)
  })
})

moduler.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
    })
    // エラー引数にnull
    callback(null, list)
  })
}

learnyounode 7問目

解答
var http = require('http')
var url = process.argv[2]

request = http.get(url, function (res) {
  res.setEncoding('utf8')
  res.on('data', console.log)
  res.on('err', console.log)
})

request.on('error', function (err) {
  console.error(err)
})

公式の解答

var http = require('http')

    http.get(process.argv[2], function (response) {
      response.setEncoding('utf8')
      response.on('data', console.log)
      response.on('error', console.error)
    }).on('error', console.error)

learnyounode 8問目

1つ目のコマンドライン引数は URL 文字列。
そのURL文字列を使ってHTTPのデータをロードする。

サーバから全て(最初のイベントだけではなく)のデータを集め、
次の2行をコンソールに出力する。

1行目は文字数、2行目はサーバから受け取った全てのデータを文字列。

解答
var http = require('http')
var url = process.argv[2]

request = http.get(url, function (res) {
  var result = ''
  res.setEncoding('utf8')
  res.on('data', function (data) {
    result += data
  })
  // 出力 endイベント
  res.on('end', function () {
    console.log(result.length)
    console.log(result)
  })
  res.on('error', console.error)
})

request.on('error', function (err) {
  console.error(err)
})

公式の解答

    var http = require('http')
    var bl = require('bl')

    http.get(process.argv[2], function (response) {
      response.pipe(bl(function (err, data) {
        if (err) {
          return console.error(err)
        }
        data = data.toString()
        console.log(data.length)
        console.log(data)
      }))
    })

learnyounode 9問目

解答
var http = require('http')
// 入力された配列の3,4,5行めを読み込む
var links = [2, 3, 4]
var buffer = []

// 即時関数 引数indexに0を入れて実行
(function render(index) {
  http.get(process.argv[links[index]], function (response) {
    response.setEncoding('utf8')
    response.on('data', function (data) {
      if (buffer[index] === undefined) {
        buffer[index] = ''
      }
      buffer[index] += data
    })
    response.on('end', function () {
      var newIndex = index + 1
      if (links[newIndex] !== undefined) {
        // links配列の中身が終わるまで繰り返し
        render(newIndex)
      } else {
        // 3つとも終わった時
        return renderOutput()
      }
    });
    response.on('error', console.error)
  }).on('error', console.error)
})(0); //self-calling function

function renderOutput() {
  buffer.forEach(function (elem) {
    console.log(elem)
  })
}

公式の解答

var http = require('http')
    var bl = require('bl')
    var results = []
    var count = 0

    function printResults () {
      for (var i = 0; i < 3; i++) {
        console.log(results[i])
      }
    }

    function httpGet (index) {
      http.get(process.argv[2 + index], function (response) {
        response.pipe(bl(function (err, data) {
          if (err) {
            return console.error(err)
          }

          results[index] = data.toString()
          count++

          if (count === 3) {
            printResults()
          }
        }))
      })
    }

    for (var i = 0; i < 3; i++) {
      httpGet(i)
    }

learnyounode 10問目

// インストール
// npm install dateformat

var net = require('net')
var dateformat = require('dateformat');
var port = process.argv[2]

var server = net.createServer(function(socket) {
    // socket handling logic
    var now = new Date();
    socket.end(dateformat(now, 'yyyy-mm-dd HH:MM') + "\n")
})
server.listen(port)

解答

var net = require('net')

    function zeroFill (i) {
      return (i < 10 ? '0' : '') + i
    }

    function now () {
      var d = new Date()
      return d.getFullYear() + '-' +
        zeroFill(d.getMonth() + 1) + '-' +
        zeroFill(d.getDate()) + ' ' +
        zeroFill(d.getHours()) + ':' +
        zeroFill(d.getMinutes())
    }

    var server = net.createServer(function (socket) {
      socket.end(now() + '\n')
    })

    server.listen(Number(process.argv[2]))

Promise

JavaScript Promiseの本

  • 同期処理が終わった後に非同期処理(イベントループ)
  • 同期処理を書きすぎない、イベントループを止めるな