コールバックは同期プログラミングにおけるreturn文に相当するもので、非同期プログラミングでは必須となる、処理結果を通知するための関数である。

継続渡しスタイル

処理結果をreturn文ではなくコールバック関数の呼び出しという形で返すことで、結果を伝播させる手法を継続渡しスタイル(CPS:continuation-passing style)という。

function add(a, b, callback) {
    callback(a + b)
}

console.log('before')
add(1, 2, result => console.log('result: ' + result))
console.log('after')
// 結果:
/**
 * before
 * result: 3
 * after
 */
function addAsync(a, b, callback) {
    setTimeout(() => callback(a + b), 100)
}

console.log('before')
addAsync(1, 2, result => console.log('result: ' + result))
console.log('after')
// 結果:
/**
 * before
 * after
 * result: 3
 */

同期と非同期の混在

const fs = require('fs')
const cache = {}

// filenameから読み込んだデータをcallbackを呼び出して処理
function inconsistentRead(filename, callback) {
    if (cache[filename]) {
        // キャッシュがある場合は同期
        callback(cache[filename])
    } else {
        // キャッシュがない場合は非同期
        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('/Users/tomixy/myWorkSpace/TeX/JavaScript/Node_jsデザインパターン/sample/ch02/04_callback_unpredictable/data.txt')

reader1.onDataReady(data => {
    console.log('First call data: ' + data)
    
    // 同じファイルを再度読み込む
    // この時点ではキャッシュが存在しているため、incosistentReadは即座に実行される
    // しかし、まだlistenersが空なので、何も実行されない
    const reader2 = createFileReader('/Users/tomixy/myWorkSpace/TeX/JavaScript/Node_jsデザインパターン/sample/ch02/04_callback_unpredictable/data.txt')
    
    // ここでようやくlistenersを登録
    reader2.onDataReady(data => {
        console.log('Second call data: ' + data)
    })
})

// 結果:
/**
 * First call data: some data
 * 
 */
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]
    }
}

/* -------------------------------------------------------------------------- */

console.log(consistentReadSync('/Users/tomixy/myWorkSpace/TeX/JavaScript/Node_jsデザインパターン/sample/ch02/05_callback_sync_api/data.txt'))
console.log(consistentReadSync('/Users/tomixy/myWorkSpace/TeX/JavaScript/Node_jsデザインパターン/sample/ch02/05_callback_sync_api/data.txt'))

// 結果:
/**
 * some data
 * 
 * some data
 * 
 */