Chuyển đến nội dung chính

レッスン 12: イベント、タイマー、診断

EventEmitter パターン、カスタム イベント、メモリ リーク検出。 AbortController/AbortSignal。診断チャネル、async_hooks。 Node.js インスペクター、ヒープ スナップショット。

💻 プログラミング — レッスン 12 レッスン 12: イベント、タイマー、診断

Node.js コア: 基本から高度まで

パート 3: 同時実行性とネットワーキング

xdev.asia

1. イベントエミッタパターン

import { EventEmitter } from 'node:events'

// Typed EventEmitter
interface OrderEvents {
  created: [order: { id: string; total: number }]
  paid: [orderId: string, amount: number]
  shipped: [orderId: string]
  error: [error: Error]
}

class OrderService extends EventEmitter<OrderEvents> {
  async createOrder(data: { items: string[]; total: number }) {
    const order = { id: crypto.randomUUID(), total: data.total }
    this.emit('created', order)
    return order
  }
}

const service = new OrderService()

service.on('created', (order) => {
  console.log(`Order ${order.id} created: $${order.total}`)
})

// Once — lắng nghe 1 lần
service.once('paid', (orderId, amount) => {
  console.log(`${orderId} paid $${amount}`)
})

// Async iterator
const ac = new AbortController()
for await (const [order] of EventEmitter.on(service, 'created', { signal: ac.signal })) {
  console.log(`New order: ${order.id}`)
}

2. AbortController と AbortSignal

// Timeout signal
const signal = AbortSignal.timeout(5000)

// Combine signals
const controller = new AbortController()
const combined = AbortSignal.any([
  controller.signal,
  AbortSignal.timeout(10000),
])

// Sử dụng với EventEmitter
const emitter = new EventEmitter()
const promise = EventEmitter.once(emitter, 'data', { signal: combined })

// Cancel
controller.abort('User cancelled')

3. 診断チャネル

import diagnostics_channel from 'node:diagnostics_channel'

// Subscribe to HTTP requests
diagnostics_channel.subscribe('http.client.request.start', (message) => {
  const { request } = message as any
  console.log(`HTTP → ${request.method} ${request.host}${request.path}`)
})

// Custom channel
const channel = diagnostics_channel.channel('myapp:db:query')

channel.subscribe((message) => {
  const { query, duration } = message as { query: string; duration: number }
  if (duration > 100) {
    console.warn(`Slow query (${duration}ms): ${query}`)
  }
})

// Publish
channel.publish({ query: 'SELECT * FROM users', duration: 150 })

4. メモリリークの検出

import v8 from 'node:v8'
import { writeFileSync } from 'node:fs'

// Heap snapshot
function takeHeapSnapshot() {
  const snapshotStream = v8.writeHeapSnapshot()
  console.log(`Heap snapshot: ${snapshotStream}`)
}

// Memory monitoring
setInterval(() => {
  const { heapUsed, heapTotal, rss } = process.memoryUsage()
  console.log({
    heapUsed: `${(heapUsed / 1e6).toFixed(1)}MB`,
    heapTotal: `${(heapTotal / 1e6).toFixed(1)}MB`,
    rss: `${(rss / 1e6).toFixed(1)}MB`,
  })

  // Alert if memory exceeds threshold
  if (heapUsed > 500 * 1e6) {
    takeHeapSnapshot()
    console.error('Memory threshold exceeded!')
  }
}, 10000)

// EventEmitter leak warning
const emitter = new EventEmitter()
emitter.setMaxListeners(20) // Default: 10

5. Node.js インスペクター API

import inspector from 'node:inspector/promises'

// Programmatic profiling
const session = new inspector.Session()
session.connect()

await session.post('Profiler.enable')
await session.post('Profiler.start')

// ... run code to profile ...

const { profile } = await session.post('Profiler.stop')
// Save profile for Chrome DevTools

次の記事: HTTP フレームワークを最初から構築する — ルーター、ミドルウェア パイプライン。