Skip to main content

Command Palette

Search for a command to run...

Async Code in Node.js: Callbacks and Promises

Published
3 min read
Async Code in Node.js: Callbacks and Promises

Overview

We all know that JavaScript is a single-threaded language, and due to that, when a time-consuming task like file read/write or making a web request can block the whole thread, we can encounter the convoy effect.

To avoid problems, we have an event loop that smartly handles this task by creating a thread pool. Which help us to do multiple tasks simultaneously with only a single thread. And this behaviour is called Asynchronous behaviour. To do this in JavaScript, we need to write async code.

Why async/await Was Introduced

Before the async/wait was introduced in JavaScript, developers mainly relied on Promises to handle asynchronous operations. Promises improved things compared to callbacks; still, they had some drawbacks:

Problems with Promises:

  • Chained .then() call reduces the code readability.

  • In the case of nested logic, the code becomes a message

  • Handling errors becomes messy

  • Debuging become hard

Solution: async/await

Async/await was introduced (ES2017) as syntactic sugar over promises. It doesn’t replace promises—it makes them easier to use and read.

This allows developers to write asynchronous code like synchronus manner.

How Async Functions Work

An async A function is simply a function that always returns a promise.

Basic Syntax

async function greet() {
  return "Hello";
}

Even though we return a string, JavaScript automatically wraps it inside a promise:

greet().then(console.log); // "Hello"

Equivalent:

function greet() {
  return Promise.resolve("Hello");
}

Key Point

  • Async Keyword make sure that the function always returns a promise.

  • You can only use await inside an async function

  • It simplifies promise handling.

Await Keyword Concept

The await keyword is what makes async/await powerful.

It pauses the execution of the async function until the promise is resolved or rejected.

Example

async function getData() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();
  console.log(data);
}

What’s happening?

  1. fetch() returns a promise

  2. await pauses execution until the promise resolves

  3. Once resolved, it gives the actual value

  4. Execution continues

Important Note

await does NOT block the entire JavaScript thread

It only pauses execution inside that async function, while the event loop continues running other tasks.

Error Handling with Async Code

One of the biggest advantages of async/await is clean error handling using try...catch.

Using Promises

fetchData()
  .then((data) => processData(data))
  .catch((err) => console.error(err));

Using Async/Await

async function run() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

Observation

  • With async/await, the code readability and scalability both improved.

  • We can now centralize error handling.

  • It becomes easy to debug the code

Key Differences

Feature Promises Async/Await
Readability Medium High
Error handling .catch() try...catch
Debugging Harder Easier
Structure Chain-based Sequential style

When NOT to Use Await

Async/await is great, but not always necessary.

Parallel Execution Issue

await task1();
await task2();

This runs tasks sequentially, not in parallel.

Better approach:

await Promise.all([task1(), task2()]);

So use async/await wisely depending on your needs.

Final Thoughts

Async/await is one of the best improvements in modern JavaScript. It makes asynchronous code:

  • Cleaner

  • More readable

  • Easier to debug

  • Closer to synchronous thinking

But remember:

  • It’s still based on promises

  • It doesn’t make code faster automatically

  • Understanding promises is still essential