イベントの発行とリスナー関数の登録

const EventEmitter = require('events').EventEmitter
const fs = require('fs')

function findPattern(files, regex) {
    const emitter = new EventEmitter()
    
    files.forEach(function(file) {
        fs.readFile(file, 'utf8', (err, content) => {
            if (err) {
                // ファイル読み込みにエラーが発生した場合に通知するerrorイベントの生成
                return emitter.emit('error', err)
            }
            
            // ファイル読み込みが完了した時点で通知するfilereadイベントの生成
            emitter.emit('fileread', file)
            let match
            
            if (match = content.match(regex)) {
                // 指定のパターンがファイル中に見つかった場合に通知するfoundイベントの生成
                match.forEach(elem => emitter.emit('found', file, elem))
            }
        })
    })
    
    return emitter
}

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

findPattern(
    [
        'fileA.txt', 
        'fileB.json'
    ],
    /hello \\w+/g
)
// onで特定のイベントに対してリスナー関数を登録
.on('fileread', file => console.log(file + ' was read'))
.on('found', (file, match) => console.log('Matched "' + match + '" in file ' + file))
.on('error', (err) => console.log('Error emitted: ' + err.message))

// 結果:
/**
 * fileA.txt was read
 * Matched "hello world" in file fileA.txt
 * fileB.json was read
 * Matched "hello NodeJS" in file fileB.json
 * Matched "hello you" in file fileB.json
 */

EventEmitterクラスの拡張

const EventEmitter = require('events').EventEmitter
const fs = require('fs')

class FindPattern extends EventEmitter {
    constructor(regex) {
        super()
        this.regex = regex
        this.files = []
    }
    
    addFile(file) {
        this.files.push(file)
        return this
    }
    
    find() {
        this.files.forEach(file => {
            fs.readFile(file, 'utf8', (err, content) => {
                if (err) {
                    return this.emit('error', err)
                }
                
                this.emit('fileread', file)
                
                let match
                if (match = content.match(this.regex)) {
                    match.forEach(elem => this.emit('found', file, elem))
                }
            })
        })
        
        return this
    }
}

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

const findPatternObj = new FindPattern(/hello \\w+/)

findPatternObj
.addFile('./fileA.txt')
.addFile('./fileB.json')
.find()
.on('found', (file, match) => console.log(`Matched "${match}" in file ${file}`))
.on('error', err => console.log(`Error emitted ${err.message}`))

// 結果:
/**
 * Matched "hello world" in file ./fileA.txt
 * Matched "hello NodeJS" in file ./fileB.json
 */

emitメソッドは同期関数

const EventEmitter = require('events').EventEmitter

class SyncEmit extends EventEmitter {
    constructor() {
        super()
        this.emit('ready')
    }
}

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

const syncEmit = new SyncEmit()

syncEmit.on('ready', () => console.log(`
    emitメソッドは同期関数なので、インスタンス生成時に即実行されます。「
    そのため、インスタンス生成後にリスナーを登録しても、イベントが通知されることはありません。
`))

// 結果:
/**
 * 
 */

コールバックとの使い分け

const EventEmitter = require('events').EventEmitter

// EventEmitterによる実装
function helloByEvents() {
    const eventEmitter = new EventEmitter()
    setTimeout(() => eventEmitter.emit('hello', 'Hello World!'), 100)
    return eventEmitter
}

// コールバックによる実装
function helloByCallback(callback) {
    setTimeout(() => { callback('Hello World!') }, 100)
}

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

helloByEvents().on('hello', (message) => console.log(message))
helloByCallback((message) => console.log(message))

// 結果:
/**
 * Hello World!
 * Hello World!
 */

引数がコールバック、戻り値がEventEmitter