Skip to main content

Command Palette

Search for a command to run...

Async Code in Node.js: Callbacks and Promises

Updated
5 min readView as Markdown
Async Code in Node.js: Callbacks and Promises

When I first started using Node.js, one thing confused me almost immediately.

Why does so much code seem to happen… later?

I’d read something like:

fs.readFile("data.txt", (err,data)=>{

 console.log(data);

});

console.log("Done");

And I expected:

Read file.

Show data.

Then print:

Done.

But what actually happened surprised me.

Sometimes:

Done

showed before file data.

And I remember thinking:

How is that possible?

Didn’t I write file reading first?

That was my first real encounter with asynchronous code in Node.

And honestly…

it felt weird until it clicked.


Why Async Code Exists in Node.js

This made much more sense when I stopped looking at syntax…

and started thinking about waiting.

Some operations take time.

Reading a file.

Fetching data.

Database queries.

Network requests.

Those things are not instant.

They may take a moment.

Question is:

Should Node sit there doing nothing while waiting?

That would be wasteful.

Async code exists so Node can keep moving.

That idea made everything easier to understand.


File Reading Example Made It Click

Suppose:

fs.readFile(
"notes.txt",
(err,data)=>{

 console.log(data);

}
);

console.log(
"Other code runs"
);

Node can continue doing other work while file is being read.

That’s the idea.

And once I understood that…

async stopped feeling random.


I Started Thinking Of It Like This

File reading starts.

Node does not freeze.

It keeps moving.

When file is ready…

run callback.

That’s the flow.

Very simple.


Callback-Based Async Execution

The first async style I learned was callbacks.

Which basically means:

Pass a function…

to run later.

That “run later” part is the key.

Example:

setTimeout(

function(){

 console.log(
  "Runs later"
 );

},

2000

);

That function is a callback.

It gets called later.

That’s all.


Callback Execution Chain


File Read Callback Example

Classic Node style:

const fs = require("fs");

fs.readFile(

"data.txt",

function(err,data){

 if(err){

  console.log(err);

  return;

 }

 console.log(
  data.toString()
 );

}

);

This looked messy to me initially.

But it’s really:

Start file read.

When finished…

run callback.


Then I Hit Nested Callbacks

And this is where things got ugly.

Something like:

getUser(function(user){

 getOrders(

 user.id,

 function(orders){

   getDetails(

    orders[0].id,

    function(details){

      console.log(details);

    }

   );

 }

);

});

And I remember looking at this and thinking…

why is my code drifting to the right?

Everything gets nested.

And harder to read.

This is where I first heard:

Callback hell.

And honestly…

fair name 😄


What Made It Hard

It wasn’t that callbacks were wrong.

It was readability.

Too much nesting.

Too much indentation.

Too much mental jumping.

That was the problem.


Promise-Based Async Handling

Then I learned promises.

And it felt much cleaner.

Same kind of flow:

getUser()

.then(user=>{

 return getOrders(
  user.id
 );

})

.then(orders=>{

 return getDetails(
  orders[0].id
 );

})

.then(details=>{

 console.log(details);

})

.catch(error=>{

 console.log(error);

});

Still async.

But much flatter.

That was a big improvement.


Promise Lifecycle Flow


What A Promise Felt Like To Me

This helped me.

A promise is basically:

A future result.

Something not ready yet…

but expected later.

That idea made the name make sense.


Why Promises Felt Better

Main reason?

Readability.

Honestly.

That was it.

Compare nested callbacks…

versus chain.

I’d rather debug promise code any day.

Especially at night 😄


Error Handling Felt Better Too

With callbacks:

Errors often handled everywhere.

With promises:

.catch(error=>{

 console.log(error);

});

Single place.

Cleaner.

That felt nice.


Callback vs Promise

This is how I remember it.

Callbacks:

Do this later.

Can get nested.

Messy sometimes.


Promises:

Represent future value.

Chain steps.

Cleaner flow.


That comparison helped me.


Real File Example With Promise Style

Modern Node gives promise-based ways too.

const fs = require("fs/promises");

async function readFile(){

 try{

 const data=
 await fs.readFile(
 "data.txt",
 "utf8"
 );

 console.log(data);

 }

 catch(error){

 console.log(error);

 }

}

When I first saw file handling like this…

it felt much nicer.

Though that moves toward async/await.

But it helped show evolution.


Small Practice Example

Callback:

setTimeout(

function(){

 console.log(
 "Finished"
 );

},

1000

);

Now promise:

new Promise(

resolve=>{

 setTimeout(

 ()=>resolve(
  "Finished"
 ),

 1000

 );

}

)

.then(console.log);

Same idea.

Different style.

Good exercise.


One Mistake I Made

I thought promises replace callbacks completely.

Not really.

Promises are built on solving problems callbacks exposed.

But callbacks still exist.

Important distinction.


Another Thing I Misunderstood

I thought async means “runs in random order.”

Wrong.

There is order.

Just different flow.

That misunderstanding caused me confusion early.


What Finally Made It Click

I stopped thinking:

Async code is weird delayed code.

And started thinking:

Async code is just code designed for waiting.

That changed everything.

That was the real idea.


Quick Recap

Why async exists:

  • Some work takes time

  • Node shouldn’t block waiting

  • Callbacks handle “run later” logic

  • Promises improve readability

  • Promises help manage async flow better

That’s the foundation.


Conclusion

Async code confused me a lot at first.

Mostly because I was reading it as if everything happened top to bottom immediately.

But once I understood waiting…

it made much more sense.

Callbacks introduced the idea.

Promises made it cleaner.

And honestly…

that progression felt natural.

If you remember one thing from this article, remember this:

Async code exists because waiting should not stop everything else.

That one idea explains a lot.

If you like this simple learning-style explanation,
I write more notes at
devwithsahil.hashnode.dev
and share progress on LinkedIn 🙂