Node JSのコールバックパターン

# 継続渡しスタイル（continuation-passing style : CPS）
JavaScriptにおいてコールバック関数とは、ある関数を呼び出す時に、引数として指定する関数で、FuncAの処理が完了した時にFuncAの結果を通知するために起動される関数のことを指します。
## 同期的継続渡しスタイル
```javascript:test.js
function add(a, b, callback) {
  callback(a + b)
}

console.log('before')
add(1, 2, result => console.log('Result : ' + result))
console.log('after')
```
結果

```bash:example.sh
$ node test.js
before
Result : 3
after
```
## 非同期CPS
```javascript:test.js
function addAsync(a, b, callback) {
  setTimeout(() => callback(a + b), 100)
}

console.log('before')
add(1, 2, result => console.log('Result : ' + result))
console.log('after')
```
結果

```bash:example.sh
$ node test.js
before
after
Result : 3
```
## 継続渡しではないコールバック
```javascript:test.js
const result = [1, 5, 7].map(element => element - 1)
console.log(result) // [0, 4, 6]
```

ちなみに以下のことはダイレクトスタイル（Direct Style:DS）と呼びます。

```javascript:example.js
function add(a, b) {
  return a + b
}

console.log(add(1,2)) // 3
```

# 同期処理か非同期処理か
どっちでもいいですが、まず避けなければならないのは「一貫性がないAPI」です。

## 同期と非同期の混在
もっとも危険なのは、ある条件には同期処理、ある条件には非同期処理を行う関数です。
以下のコードは一貫性がないコードの例です。

```javascript:test.js
const fs = require('fs')
const cache = {}

function inconsistentRead(filename, callback) {
  if(cache[filename]) {
     callback(cache[filename]) // cacheにデータがある場合、同期的に実行される。
  } else {
     // cacheにデータがない場合、ひい同期関数fs.readFile()を呼び出す。
     fs.readFile(filename, 'utf8', (err, data) => {
       cache[filename] = data
       callback(data)
     }
  }
}
```

## 混在がもたらす問題
```javascript:test.js
const fs = require('fs')
const cache = {}

function inconsistentRead(filename, callback) {
  if(cache[filename]) {
     callback(cache[filename]) // cacheにデータがある場合、同期的に実行される。
  } else {
     // cacheにデータがない場合、ひい同期関数fs.readFile()を呼び出す。
     fs.readFile(filename, 'utf8', (err, data) => {
       cache[filename] = data
       callback(data)
     })
  }
}

function createFileReader(filename) {
  const listeners = []
  inconsistentRead(filename, value => {
    listeners.forEach(listener => listener(value))
  })

  return {
    onDataReady: listener => listeners.push(listener)
  }
}

const reader1 = createFileReader('data.txt')
reader1.onDataReady(data => {
  console.log('First call data: ' + data)
  // しばらくしてから同じファイルを呼び出す
  const reader2 = createFileReader('data.txt')
  reader2.onDataReady(data => {
    console.log('Second call data: ' + data)
  })
})
```
結果

```bash:example.sh
$ node test.js
First call data: some data
```

## 解決策１同期APIの利用
同期関数を使えば解決できます。

```javascript:test.js
const fs = require('fs')
const cache = {}
function consistentReadSync(filename) {
  if (cache[filename]) {
    return cache[filename]
  } else {
    cache[filename] = fs.readFileSync(filename, 'utf8') // 同期関数使用
    return cache[filename]
  }
}
```
しかし、以下の留意点があります。
・ある機能に関して常に同期バージョウンが用意されているとは限らない
・他のリクエストは処理待ちとなるため、全体的にパフォーマンスが落ちる。

## 解決策2遅延実行
同期的なコールバックが「将来」起動されるようにスケジュールする。

```javascript:test.js
const fs = require('fs')
const cache = {}
function consistentReadAsync(filename) {
  if (cache[filename]) {
    process.nextTick(() => callback(cache[filename]))
  } else {
     fs.readFile(filename, 'utf8', (err, data) => {
       cache[filename] = data
       callback(data)
     }
  }
}
```

# Node.jsのコールバック
## エラーの伝播
```javascript:test.js
const fs = require('fs')
function readJSON(filename, callback) {
  fs.readFile(filename, 'utf8', (err, data) => {
    let parsed
    if (err) {
      return callback(err)
    }

    try {
      parsed = JSON.parse(data)
    } catch (err) {
      return callback(err)
    }
    callback(null, parsed)
  })
}
```

## キャッチされない例外
```javascript:test.js
const fs = require('fs')
function readJSONThrows(filename, callback) {
  fs.readFile(filename, 'utf8', (err, data) => {
    let parsed
    if (err) {
      return callback(err)
    }
    callback(null, JSON.parse(data))
  })
}

try {
  readJSONThrows('nonjson.txt', err => {
    if (err) { console.log(err) } else { JSON.stringify(json) }
  })
} catch(err) {
   console.log('こうしてもキャッチはできません。')
}
```
上記の場合、キャッチされません。なぜなら、readJSONThrowsを呼び出すスタックとコールバックを呼び出すスタックがことなるからです。
これは下記のようにコードを作ったらキャッチされます。

```javascript:test.js
readJSONThrows('nonjson.txt', err => {
  if (err) { console.log(err) } else { JSON.stringify(json) }
})

process.on('uncaughtException', (err) => {
  console.error('ここでキャッチ')
  process.exit(1) // エラーコード１で終了。これがないと実行を継続する。
})
```

参考文献
[Node.jsデザインパターン 第2版 - Mario Casciaro (著), Luciano Mammino (著), 武舎 広幸 (翻訳), 阿部 和也 (翻訳)](https://www.amazon.co.jp/Node-js%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-%E7%AC%AC2%E7%89%88-Mario-Casciaro/dp/4873118735)