Skip to content

Node.js 26 Streams リファクタリング:ReadableStream と Web Streams の統一

Node.js の Streams は大規模 I/O 処理のコアであり続けているが、Node.js Streams と Web Streams の 2 つの API が並存していることは開発者に選択の困難をもたらしていた。Node.js 26 は、下層のリファクタリングにより、後方互換性を保ちながら 2 つの API の相互運用性をよりスムーズにした。

二重 Streams 体制の現状

Node.js 26 以前、Streams には 2 つの並列システムがあった:

javascript
// Node.js Streams(従来)
const { Readable, Writable, Transform } = require('node:stream')
const fs = require('node:fs')

const readStream = fs.createReadStream('file.txt')
const writeStream = fs.createWriteStream('output.txt')
readStream.pipe(transformStream).pipe(writeStream)

// Web Streams(モダン標準)
const response = await fetch('https://api.example.com/data')
const reader = response.body.getReader()
const writable = writableStream.getWriter()

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  await writable.write(value)
}

Node.js 26 の統一戦略

Node.js 26 は「段階的統一」戦略を採用し、下層で 2 つの API のシームレスな相互運用を実現した:

ReadableStream の強化

javascript
import { ReadableStream } from 'node:stream/web'
import { createReadStream } from 'node:fs'

// Node.js 26:fs.createReadStream から直接 Web ReadableStream を作成
const nodeStream = createReadStream('large-file.txt')
const webStream = ReadableStream.from(nodeStream)

// Web ReadableStream を fetch に直接使用
const response = new Response(webStream, {
  headers: { 'Content-Type': 'text/plain' }
})

// 逆変換:Web Stream → Node Stream
const { Readable } = await import('node:stream')
const nodeReadable = Readable.fromWeb(webReadable)

TransformStream の相互運用

javascript
import { TransformStream } from 'node:stream/web'

const upperCaseTransform = new TransformStream({
  transform(chunk, controller) {
    const text = new TextDecoder().decode(chunk)
    controller.enqueue(new TextEncoder().encode(text.toUpperCase()))
  }
})

import { pipeline } from 'node:stream/promises'
import { createReadStream, createWriteStream } from 'node:fs'

await pipeline(
  createReadStream('input.txt'),
  upperCaseTransform,  // Web TransformStream を直接使用
  createWriteStream('output.txt')
)

非同期イテレーター:モダン Streams のベストプラクティス

Node.js 26 は非同期イテレーターのサポートを強化し、Streams を処理する最も直感的な方法となっている:

javascript
import { createReadStream } from 'node:fs'

async function* processLines(readable) {
  let buffer = ''
  
  for await (const chunk of readable) {
    buffer += chunk
    const lines = buffer.split('\n')
    buffer = lines.pop() || ''
    
    for (const line of lines) {
      if (line.trim()) {
        yield JSON.parse(line)
      }
    }
  }
  
  if (buffer.trim()) {
    yield JSON.parse(buffer)
  }
}

const fileStream = createReadStream('events.jsonl')
for await (const event of processLines(fileStream)) {
  console.log('Event:', event.type)
}

バックプレッシャー処理の簡略化

バックプレッシャーは Streams プログラミングで最もエラーが発生しやすい部分の一つだ。Node.js 26 はバックプレッシャー処理を簡略化した:

javascript
import { Writable } from 'node:stream'

// Node.js 26:自動バックプレッシャー処理
const writable = new Writable({
  write(chunk, encoding, callback) {
    setTimeout(() => {
      console.log('Processed:', chunk.toString())
      callback()  // コールバック完了後に次の chunk が書き込まれる
    }, 100)
  },
  highWaterMark: 16 * 1024  // 16KB
})

import { pipeline } from 'node:stream/promises'

await pipeline(
  createReadStream('large-input.bin'),
  transformStream,
  createWriteStream('output.bin')
)

エラーハンドリングのベストプラクティス

javascript
import { pipeline } from 'node:stream/promises'
import { createReadStream, createWriteStream } from 'node:fs'

async function processFile(inputPath, outputPath) {
  try {
    await pipeline(
      createReadStream(inputPath),
      transformStream,
      createWriteStream(outputPath)
    )
    console.log('File processed successfully')
  } catch (err) {
    if (err.code === 'ENOENT') {
      console.error('Input file not found')
    } else if (err.code === 'EACCES') {
      console.error('Permission denied')
    } else {
      console.error('Stream processing failed:', err.message)
    }
    throw err
  }
}

まとめ

Node.js 26 の Streams リファクタリングにより、Node.js Streams と Web Streams API の間の真の相互運用が実現された。強化された非同期イテレーター、簡略化されたバックプレッシャー処理、Fetch API との深い統合により、Streams プログラミングがより直感的で効率的になった。実際のプロジェクトでは、適切な highWaterMark の選択、エラーとリソースの適切な処理、再利用可能なデータパイプラインの構築が Streams のパフォーマンス利点を活かす鍵となる。

MIT Licensed