In this topic, you will explore the Streams Module in Node.js. Take a step further into this exciting world of data flow and get to know the Streams Promises API, pipeline, and finishing.
Here's a little reminder, streams in Node.js are objects that help manage the flow of data. In essence, streams are like conveyor belts at an assembly line, continuously moving and allowing data to be read and written in order. The Streams Module provides a more refined interface for these operations. Let's jump right into it!
The Streams promises API
In Node.js, Promises are objects that represent a future value. The Streams Promises API is a feature that allows us to work with streams in a more modern and manageable way.
Before this feature, handling streams involved lots of callbacks and potential confusion. Now, with the Streams Promises API, you can use .then(), .catch(), and async/await with your streams to improve code readability and management.
Here's an example that reads a file using node:fs, which is a built-in module and the Streams promises API in Node.js:
// Require the necessary modules
const fs = require('node:fs');
const { pipeline } = require('node:stream/promises');
async function main() {
await pipeline(
fs.createReadStream('input.txt'),
fs.createWriteStream('output.txt')
);
console.log('Pipeline succeeded.');
}
main().catch(console.error);The code begins by importing the necessary modules: node:fs to work with the file system and node:stream/promises to use the Streams Promises API. Then, in the asynchronous function main, the pipeline function reads data from input.txt and writes it to output.txt. This process is done asynchronously, and the code prints Pipeline succeeded to the console once it's done. If there's an error anywhere in this process, the program will catch it and log the error to the console using main().catch(console.error).
When discussing the Streams Promises API, it is important to remember that it's a feature in Node.js but not a built-in module like fs or zlib. It resides within the stream/promises module in the Node.js standard library.
Understanding Pipeline
A pipeline is a utility function provided by the Streams Module in Node.js to link together streams and pass data from one stream to another. Think of it as an assembly line in a factory where the product (or data, in this case) passes through several stages and each stage contributes to the final outcome.
It is essential to understand that pipelines are not just a fancier way to manage streams. They solve real-world problems that developers encounter when working with large volumes of data. So, let's delve into why you should consider using pipelines.
There are several compelling reasons to use pipelines when dealing with streams:
Handling errors: The pipeline function ensures that if an error occurs at any point in the process, all streams are destroyed to prevent memory leaks and unnecessary system load. It eliminates the need for writing extensive error-handling logic. As a result, it makes your code cleaner and easier to maintain.
Streamlining data flow: The pipeline function provides a straightforward way to pass data from a readable stream to a writable stream, or through a chain of transform streams. This makes the code easier to understand and eliminates the risk of writing data to a stream faster than it can be consumed.
Optimizing resource usage: When dealing with large data, pipelines effectively handle backpressure. This means it automatically manages the data flow rate to avoid overwhelming the system or the writable stream. It ensures that the Node.js process doesn't run out of memory when dealing with large files, making your applications more efficient.
Let's see the pipeline in action with an example. The following script reads a text file, compresses it using the zlib module's gzip function, and then writes it to a new file:
// Import the required modules
const fs = require('node:fs');
const zlib = require('node:zlib');
const { pipeline } = require('node:stream/promises');
async function compressFile() {
// Use pipeline to read, compress, and write the file
await pipeline(
// Read the file
fs.createReadStream('input.txt'),
// Compress the file
zlib.createGzip(),
// Write the compressed data to a new file
fs.createWriteStream('input.txt.gz')
);
console.log('File successfully compressed.');
}
compressFile().catch(console.error);In this example, pipeline reads data from input.txt, compresses it, and then writes the compressed data to input.txt.gz. If an error occurs at any step, pipeline will destroy all streams, ensuring no resources are wasted.
Exploring Finishing
Finishing refers to the completion of data transfer. A stream is considered finished when no more data will be written into it. Similarly, a stream is ended when no more data will be read from it.
In the context of the Stream Module, you should typically use the finished function to detect when a stream finishes emitting data. The finished function returns a Promise that will be fulfilled when all data has been flushed to the underlying system.
Here's an example:
const { finished } = require("node:stream/promises");
const fs = require("node:fs");
async function run() {
const stream = fs.createReadStream("file.txt");
stream.on("data", (chunk) => {
// Process the chunk (in this example, we're just consuming it)
console.log(`Received ${chunk.length} bytes of data.`);
});
await finished(stream);
console.log("Stream is finished");
}
run().catch(console.error);In the example shown above, the program creates a ReadStream from file.txt and then uses the finished function to detect when the stream has completed emitting data. Once the stream is finished, the message Stream is finished gets logged to the console. Errors that may occur during the execution are handled and logged using .catch(console.error).
Conclusion
Let's recap what you've learned today:
Streams Promises API lets us handle streams using promises and enables you to use the more comfortable
.then(),.catch(), andasync/awaitsyntax.The pipeline function is a tool that enables us to compose streams easily and robustly while ensuring that all streams are properly cleaned up.
Finishing is the concept of a stream completing its data emission. You can use the
finishedfunction to detect this event.
Remember, just like with all things programming, practice makes perfect. Don't be afraid to play around with the code examples and experiment with these concepts. Now, you're well-equipped to handle streams in Node.js more efficiently.