The what#
It’s very likely that when you are looking for a Node.js Stream code, you find something similar to this:
fs.createReadStream('./data.txt')
.pipe(writableStream)
.on('data', (data) => {
// do something with the data
})
.on('end', () => {
// done writing
})
The chainability of the API makes the code look clean. You like it and copy-paste it to your project. However, you notice something missing here. Ah, error handling! So you add it and now the code looks like this:
fs.createReadStream('./data.txt')
.pipe(writableStream)
.on('data', (data) => {
// do something with the data
})
.on('end', () => {
// done writing
})
.on('error', (err) => {
console.error('nothing escapes my traps!', err)
})
Pleased with the carefulness you put into your code, you push this to the main branch and deploy the project. 5 hours later, your boss knocks at your inbox with a screenshot of the logs.
events.js:292
throw er; // Unhandled 'error' event
You:
The why#
A lot of people (including me) at first glance think this code is complete and error-free. We’re fooled by the beauty of chainability and forget that there are 2 stream objects in this code: a readable stream and a writable stream.
const readableStream = fs.createReadStream('./data.txt')
readableStream
.pipe(writableStream)
...
The pipe()
method on the readableStream
returns the writableStream
for chaining. In fact, in the code above, the readableStream
has no event handlers, hence the Unhandled 'error' event
log.
All you need to do is to add another error handler for the readableStream
.
const readableStream = fs.createReadStream('./data.txt')
readableStream
.on('error', err => {
// handle error when reading file
})
.pipe(writableStream)
...
The notes#
One more thing to note: when the readableStream
emits an error
event, the writableStream
is not automatically closed. So you always need to manually close writableStream
to prevent memory leaks.
const readableStream = fs.createReadStream('./data.txt')
readableStream
.on('error', err => {
// handle error when reading file
writableStream.close()
})
.pipe(writableStream)
...