# Async Code in Node.js: Callbacks and Promises

In this article, we’ll understand one of the most important concepts in Node.js — asynchronous code.

## What we are going to learn

*   Why async code exists in Node.js
    
*   Callback-based async execution
    
*   Problems with nested callbacks
    
*   Promise-based async handling
    
*   Benefits of promises
    

* * *

## What is Async Code and Why It Exists

Let’s start with a basic question:

Why doesn’t Node.js execute everything line by line like traditional programs?

Because Node.js is single-threaded.

This means:

*   It can run only one task at a time
    
*   But it still needs to handle multiple users, requests, file operations, and API calls
    

Now imagine this:

```javascript
const data = readFileSync("largeFile.txt");
console.log(data);
```

If the file is large, Node.js will:

*   Stop execution
    
*   Wait until the file is fully read
    
*   Then move forward
    

This creates a problem:

While waiting, the server cannot handle other requests.

* * *

## Solution: Asynchronous Code

Instead of waiting, Node.js uses async code.

The idea is simple:

Start a task, and when it finishes, notify me. Meanwhile, I will continue doing other work.

Example:

```javascript
readFile("file.txt", (err,data) => {
console.log(data);
});

console.log("This runs first");
```

Output:

```javascript
This runs first
(file content later)
```

Node.js does not block execution, which makes it fast and efficient.

* * *

## Callback-Based Async Execution

The first approach to handling async operations in Node.js was callbacks.

A callback is simply a function passed as an argument to another function, which is executed later.

Example:

```typescript
function fetchData(callback) {
setTimeout(() => {
callback("Data received");
  },2000);
}

fetchData((data) => {
console.log(data);
});
```

Flow:

1.  The async task starts
    
2.  Node.js continues execution
    
3.  Once the task finishes, the callback runs
    

* * *

## Problems with Nested Callbacks (Callback Hell)

Now consider multiple dependent async operations:

```javascript
loginUser((user) => {
getProfile(user, (profile) => {
getPosts(profile, (posts) => {
console.log(posts);
    });
  });
});
```

This structure is called callback hell.

Problems:

*   Code becomes deeply nested
    
*   Hard to read and understand
    
*   Debugging becomes difficult
    
*   Error handling is inconsistent
    

This pattern is often called the "pyramid of doom".

* * *

## Promise-Based Async Handling

To solve the problems of callbacks, JavaScript introduced promises.

A promise represents a value that will be available in the future.

It has three states:

*   Pending
    
*   Resolved
    
*   Rejected
    

* * *

## Example using Promise

```javascript
function fetchData() {

return new Promise((resolve,reject) => {

setTimeout(() => {
resolve("Data received");
    },2000);
  });
}

fetchData()
.then((data) => {
console.log(data);
  })
.catch((err) => {
console.error(err);
  });
```

* * *

## Chaining Promises

```javascript
loginUser()
.then((user) =>getProfile(user))
.then((profile) =>getPosts(profile))
.then((posts) =>console.log(posts))
.catch((err) =>console.error(err));
```

This removes nesting and creates a clear, linear flow.

* * *

## Benefits of Promises

### 1\. Better readability

Code is flatter and easier to understand compared to nested callbacks.

### 2\. Centralized error handling

Instead of handling errors at every step, a single `.catch()` can handle failures.

### 3\. Easy chaining of async operations

Multiple async steps can be connected in a clean sequence.

### 4\. Foundation for async/await

Promises enable modern syntax like async/await:

```javascript
async function run() {
const data = await fetchData();
console.log(data);
}
```

This looks like synchronous code but works asynchronously.

* * *

## Final Thoughts

*   Node.js uses async code to avoid blocking execution
    
*   Callbacks were the first solution but led to complex and unreadable code
    
*   Promises improved structure and made async code easier to manage
    
*   Today, async/await is the most commonly used approach, built on top of promises
