Node.js ストリーム エラー処理 解説
Node.js のストリームは、データの非同期なフローを扱うための強力なツールです。しかし、ストリームの操作中にエラーが発生する可能性があります。適切なエラー処理は、アプリケーションの安定性を確保するために重要です。
エラーイベントのリスニング
ストリームは EventEmitter を継承しているため、error
イベントをリスニングすることでエラーをキャッチできます。
const fs = require('fs');
const Readable = require('stream').Readable;
const readableStream = fs.createReadStream('nonexistent_file.txt');
readableStream.on('error', (err) => {
console.error('Error reading file:', err);
});
パイプラインにおけるエラー処理
ストリームをパイプラインで繋げるとき、エラーはパイプラインのどこで発生しても伝播します。各ストリームで error
イベントをリスニングして、適切なエラー処理を行う必要があります。
const fs = require('fs');
const zlib = require('zlib');
const readableStream = fs.createReadStream('large_file.txt');
const gzipStream = zlib.createGzip();
const writableStream = fs.createWriteSt ream('large_file.txt.gz');
readableStream.pipe(gzipStream).pipe(writableStream);
readableStream.on('error', (err) => {
console.error('Error reading file:', err);
});
gzipStream.on('error', (err) => {
console.error('Error compressing data:', err);
});
writableStream.on('error', (err) => {
console.error('Error writing to file:', err);
});
エラーの伝播と終了
ストリームのエラーは、パイプラインを上流に伝播します。エラーが発生すると、ストリームは通常終了します。
エラーハンドリングのベストプラクティス
- ユーザーにエラーを通知する
ユーザーにエラーを通知する場合は、わかりやすいメッセージを表示してください。 - エラーを回復可能な場合は回復する
一部のエラーは回復可能かもしれません。例えば、ファイルが存在しない場合は、ファイルを作成するなどの処理を行うことができます。 - エラーを適切に伝播させる
エラーを適切に伝播させ、アプリケーションの適切なレベルで処理してください。 - エラーを適切にログする
エラーメッセージをログに記録することで、デバッグやトラブルシューティングに役立ちます。 - 常に error イベントをリスニングする
すべてのストリームでerror
イベントをリスニングして、エラーを適切に処理してください。
ファイル読み込みエラーの例
const fs = require('fs');
const readableStream = fs.createReadStream('nonexistent_file.txt');
readableStream.on('error', (err) => {
console.error('ファイル読み込みエラー:', err);
});
- console.error
エラーメッセージをコンソールに出力します。 - on('error')
ストリームでエラーが発生した場合に呼び出されるイベントリスナーを設定します。 - fs.createReadStream
指定したファイルを読み込むための Readable ストリームを作成します。
この例では、存在しないファイルを読み込もうとするため、error
イベントが発生し、エラーメッセージが出力されます。
const fs = require('fs');
const zlib = require('zlib');
const readableStream = fs.createReadStream('large_file.txt');
const gzipStream = zlib.createGzip();
const writableStream = fs.createWriteSt ream('large_file.txt.gz');
readableStream.pipe(gzipStream).pipe(writableStream);
// 各ストリームでエラーイベントをリスニング
readableStream.on('error', (err) => {
console.error('ファイル読み込みエラー:', err);
});
gzipStream.on('error', (err) => {
console.error('圧縮エラー:', err);
});
writableStream.on('error', (err) => {
console.error('ファイル書き込みエラー:', err);
});
- 各ストリームでの on('error')
各ストリームでエラーが発生した場合に、それぞれのエラーに対応した処理を行います。 - pipe
ストリームを繋ぎ、データを順次処理します。
この例では、ファイルの読み込み、圧縮、書き込みの各段階でエラーが発生する可能性があり、それぞれのストリームで error
イベントをリスニングすることで、エラーの原因を特定し、適切な処理を行うことができます。
ストリームのエラーは、パイプラインを上流に伝播します。例えば、writableStream
でエラーが発生した場合、gzipStream
と readableStream
にも影響が及ぶ可能性があります。
// writableStream でエラーが発生した場合、パイプライン全体が停止する可能性がある
Node.js のストリームでは、error
イベントをリスニングすることで、エラーを検知し、適切な処理を行うことができます。パイプラインで複数のストリームを繋ぐ場合、各ストリームでエラーが発生する可能性があるため、すべてのストリームで error
イベントをリスニングすることが重要です。
エラー処理のベストプラクティス
- カスタムエラー
独自のエラーオブジェクトを作成することで、より詳細なエラー情報を扱うことができます。 - stream.pipeline
Node.js v10 以降では、stream.pipeline
を使用することで、パイプラインのエラー処理を簡潔に記述できます。 - async/await との組み合わせ
async/await
を使用すると、より直感的なエラー処理を行うことができます。
async/await と try-catch
- 例
- シンプルで直感的なエラー処理
非同期処理を同期的に記述できるasync/await
と、エラーを捕捉するtry-catch
を組み合わせることで、エラー処理をより直感的に行うことができます。
const fs = require('fs').promises;
async function readFile() {
try {
const data = await fs.readFile('nonexistent_file.txt');
console.log(data);
} catch (err) {
console.error('エラー:', err);
}
}
readFile();
- メリット
error
イベントをリスナー登録する必要がないため、コードが簡潔になるtry-catch
ブロック内でエラーの種類に応じて異なる処理を行うことができる
Promise
- エラー処理の標準的な方法
Promise は、非同期処理の結果を表すオブジェクトです。then
メソッドで成功時の処理を、catch
メソッドで失敗時の処理を記述します。
const fs = require('fs').promises;
fs.readFile('nonexistent_file.txt')
.then(data => {
console.log(data);
})
.catch(err => {
console.error('エラー:', err);
});
- メリット
async/await
と同様に、エラー処理をシンプルに記述できる- Promise チェーンで複数の非同期処理を繋げることができる
stream.pipeline (Node.js v10 以降)
- パイプライン全体のエラー処理
stream.pipeline
は、複数のストリームをパイプラインで繋ぎ、エラーが発生した場合にパイプライン全体を停止させることができます。
const fs = require('fs');
const zlib = require('zlib');
const { pipeline } = require('stream');
pipeline(
fs.createReadStream('large_file.txt'),
zlib.createGzip(),
fs.createWriteStream('large_file.txt.gz'),
(err) => {
if (err) {
console.error('エラー:', err);
}
}
);
- メリット
- パイプライン全体のエラー処理を簡潔に記述できる
- エラーが発生した場合に、パイプラインを自動的に停止させることができる
カスタムエラー
- 詳細なエラー情報の提供
独自のエラーオブジェクトを作成することで、エラーの種類や原因をより詳細に表すことができます。
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
// ...
throw new MyError('カスタムエラーが発生しました');
- メリット
- エラーの種類に応じて異なる処理を行うことができる
- デバッグに役立つ
どの方法を選ぶべきか?
- 詳細な情報
カスタムエラーは、エラーの種類や原因を詳細に把握したい場合に有効です。 - パイプライン
stream.pipeline
は、複数のストリームを繋ぐ場合に便利です。 - シンプルさ
async/await
や Promise は、シンプルで直感的なエラー処理が可能です。
Node.js のストリームにおけるエラー処理には、複数の方法があります。それぞれの方法にメリットとデメリットがあり、状況に応じて適切な方法を選択することが重要です。
- 詳細なエラー情報
カスタムエラー - パイプラインのエラー処理
stream.pipeline
- シンプルなエラー処理
async/await
や Promise
これらの方法を組み合わせることで、より堅牢なアプリケーションを開発することができます。
- エラー監視
エラー発生時に通知を送信したり、ログに記録したりすることで、問題を早期に検知することができます。 - エラー回復
一部のエラーは、再試行やエラー処理によって回復できる場合があります。 - エラーバブルアップ
エラーは、ストリームを上流に伝播します。
node.js stream