Node.js: Code Outside the Browser / Async Programming in Node.js

Reading and Writing File Asynchronously

🎯 Goal of This Note

Learn how to read and write files using the asynchronous (non-blocking) versions of fs functions, and understand how to chain multiple dependent async steps using callbacks.


πŸ‘·β€β™‚οΈ Setup

  1. πŸ“₯ Grab the starter files for this section and open the project folder.

    Download the Repo & Open starter folder inside : 1-node-farm
    https://github.com/codevedadev/complete-node-bootcamp

  2. πŸ“„ Create a new file: index.js

  3. If you already have it open, skip this step.


πŸ“₯ Step 1: Reading a File Asynchronously

  • Add this Code block

fs.readFile('./txt/start.txt','utf8',(err, data)=>{
   console.log(data);
});
  • In Terminal Type: node index.js

The avocado πŸ₯‘ is popular in vegetarian cuisine as a substitute for meats in sandwiches and salads because of its high fat content πŸ˜„
File written!
read-this

πŸ” Breakdown

  • We still use the fs module, but this time the function is readFile (not readFileSync).

  • Arguments passed to readFile:

    1. πŸ“ Path to the file β€” e.g. ./txt/start.txt

    2. πŸ”€ Character encoding β€” e.g. 'utf8' (recommended to include, to be safe)

    3. πŸ” Callback function β€” runs once the file has finished reading

πŸ”„ The Callback Function Pattern

(err, data)=>{...}
  • The callback receives two arguments:

    1. ❌ err β€” the error, if one occurred

    2. πŸ“„ data β€” the actual file content

  • This is called the "error-first callback" style β€” extremely common in Node.js.

    • The error is always the first parameter, and the actual result (data) comes second.


πŸš€ Step 2: Proving It's Non-Blocking

To demonstrate that readFile doesn't block execution, add logs before and after:

fs.readFile('./txt/start.txt','utf8',(err, data)=>{
  console.log('Read this!');
});

console.log('Will read file!');

What do you think will be printed first❓

  • Type node index.js in terminal

Will read file!
Read this!

πŸ”— Step 3: Chaining Multiple Async Steps (Dependent Reads)

Sometimes one file read depends on the result of a previous one. Example scenario:

  1. πŸ“„ Read start.txt β†’ its content tells you the name of the next file to read.

  2. πŸ“– Read that file β†’ its content needs to be combined with another file's content.

  3. ✍️ Write the final combined result into a new output file.

πŸ’» Code

fs.readFile('./txt/start.txt', 'utf8', (err, data1) => {
  fs.readFile(`./txt/${data1}.txt`, 'utf8', (err, data2) => {
    console.log(data2);

    fs.readFile('./txt/append.txt', 'utf8', (err, data3) => {
      console.log(data3);

      fs.writeFile('./txt/final.txt', `${data2}\n${data3}`, 'utf8', (err) => {
        console.log('Your file has been written πŸŽ‰');
      });
    });
  });
});

console.log('Will read file!');
  • Terminal Output

  • final.txt Output

πŸ”„ How It Works Step-by-Step

  1. πŸ“„ First read: start.txt is read β†’ result stored as data1

    data1 contains the name of another file (e.g., read-this)

  2. πŸ“– Second read: Using data1, Node reads ./txt/read-this.txt
    (built dynamically using a template string) β†’ result stored as data2.

  3. πŸ“‘ Third read: Reads append.txt β†’ result stored as data3.

  4. ✍️ Write step: Combines data2 and data3 (with a newline \n between them) and writes it to final.txt.

πŸ“Œ Important Notes

  • Each step is nested inside the callback of the previous step β€” this is necessary because each step depends on data from the one before it.

  • This nested pattern is called "callback hell" (mentioned in the previous note) β€” it works, but becomes hard to read as more steps are added.


πŸ“€ Step 4: Writing a File Asynchronously

πŸ’» Code

fs.writeFile('./txt/final.txt', textToWrite,'utf8',(err)=>{
  console.log('Your file has been written πŸŽ‰');
});

πŸ” Breakdown

  • Just like readFile, we use the asynchronous version: writeFile (not writeFileSync).

  • Arguments:

    1. πŸ“ Path to the file to write β€” ./txt/final.txt

    2. πŸ“ The content to write into the file

    3. πŸ”€ Encoding β€” 'utf8'

    4. πŸ” Callback function β€” runs once writing is complete

βš–οΈ Difference from readFile's Callback

  • writeFile's callback only receives one argument: err (the error, if any).

  • There's no data to receive here, since we're writing, not reading.


⚠️ Step 5: Handling Errors

It's good practice to check for errors inside callbacks:

fs.readFile('./txt/start.txt', 'utf8', (err, data1) => {
  if (err) {
    console.log('ERROR πŸ’₯');
    return;
  }
  ......

Let’s simulate an error:

  • Rename start.txt to starttttt.txt.

  • Run the application again.

  • Observe the error output in the terminal.

  • Terminal Output

πŸ’‘ Why This Matters

  • ❌ If the file doesn't exist (or another error occurs), err will contain error details.

  • πŸ›‘ Using return after logging the error stops the rest of the callback from running β€” preventing your code from trying to use data1 when it doesn't actually exist.

  • πŸ§ͺ Test this by temporarily giving a wrong file name β€” you'll see the error log instead of a crash, and the rest of the code will be skipped safely.


βœ… Full Code

const fs = require("fs");

// Blocking, Synchronous way
// const textIn = fs.readFileSync("./txt/input.txt", "utf8");
// console.log(textIn);

// const textOut = `This is what we know about the avocado:${textIn}. Created on${Date.now()}`;
// fs.writeFileSync("./txt/output.txt", textOut);
// console.log("File written!");

// Non blocking, Asynchronous way

fs.readFile("./txt/start.txt", "utf8", (err, data1) => {
  fs.readFile(`./txt/${data1}.txt`, "utf8", (err, data2) => {
    console.log(data2);

    fs.readFile("./txt/append.txt", "utf8", (err, data3) => {
      console.log(data3);

      fs.writeFile("./txt/final.txt", `${data2}\n${data3}`, "utf8", (err) => {
        console.log("Your file has been written πŸŽ‰");
      });
    });
  });
});

console.log("Will read file!");
const fs = require("fs");

// Blocking, Synchronous way
// const textIn = fs.readFileSync("./txt/input.txt", "utf8");
// console.log(textIn);

// const textOut = `This is what we know about the avocado:${textIn}. Created on${Date.now()}`;
// fs.writeFileSync("./txt/output.txt", textOut);
// console.log("File written!");

// Non blocking, Asynchronous way
fs.readFile("./txt/starttttttt.txt", "utf8", (err, data1) => {
  if (err) return console.log("ERROR! ❀️πŸ’₯");

  fs.readFile(`./txt/${data1}.txt`, "utf8", (err, data2) => {
    console.log(data2);

    fs.readFile("./txt/append.txt", "utf8", (err, data3) => {
      console.log(data3);

      fs.writeFile("./txt/final.txt", `${data2}\n${data3}`, "utf8", (err) => {
        console.log("Your file has been written πŸŽ‰");
      });
    });
  });
});
console.log("Will read file!");