Skip to main content

Command Palette

Search for a command to run...

Async Code in Node.js: Callbacks and Promises

Updated
2 min readView as Markdown

1. Why async code exists in Node.js

  • Node.js runs on a single-threaded event loop

  • Blocking operations (like file I/O, network calls) would freeze the app

  • Async lets Node:

    • Handle multiple tasks efficiently

    • Stay responsive under load


2. Start with a simple file reading scenario

Callback-based approach

const fs = require('fs');

fs.readFile('data.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file');
    return;
  }
  console.log('File content:', data);
});

3. Callback flow (step-by-step)

  1. readFile is called

  2. Node registers the callback

  3. File read happens in background (non-blocking)

  4. Once done → callback pushed to event loop queue

  5. Event loop executes callback


4. Problems with nested callbacks (Callback Hell)

fs.readFile('file1.txt', 'utf8', (err, data1) => {
  if (err) return;

  fs.readFile('file2.txt', 'utf8', (err, data2) => {
    if (err) return;

    fs.readFile('file3.txt', 'utf8', (err, data3) => {
      if (err) return;

      console.log(data1, data2, data3);
    });
  });
});

Issues:

  • Hard to read 😵

  • Difficult error handling

  • Poor scalability

  • Pyramid-shaped code


5. Promise-based async handling

const fs = require('fs').promises;

fs.readFile('file1.txt', 'utf8')
  .then(data1 => {
    console.log(data1);
    return fs.readFile('file2.txt', 'utf8');
  })
  .then(data2 => {
    console.log(data2);
    return fs.readFile('file3.txt', 'utf8');
  })
  .then(data3 => {
    console.log(data3);
  })
  .catch(err => {
    console.error('Error:', err);
  });

6. Benefits of Promises

  • Cleaner chaining (no nesting)

  • Centralized error handling (.catch)

  • Better readability

  • Easier to extend and debug


7. Callback vs Promise (Quick Comparison)

Aspect Callback Promise
Structure Nested Chain-based
Readability Poor (deep nesting) Cleaner
Error handling Multiple checks Single .catch()
Scalability Difficult Easier

8. Flow

Callback Execution Chain

readFile → callback → readFile → callback → readFile → callback
        (nested inside each other)

Promise Lifecycle Flow

Pending → Fulfilled (.then)
        → Rejected (.catch)

Final Suggestion

  • Avoid deep callbacks

  • Prefer Promises (or even better: async/await)

  • Keep async code flat and readable

More from this blog

aalampatilblogs

52 posts