HVRDHVRD
JavaScript

Promises, Async/Await

Explore Promises, Async/Await, Promise.all, Promise.race, and proper error handling in asynchronous JavaScript.

Promises

A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. It acts as a placeholder for a value that will be available in the future.

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Data received");
  }, 1000);
});

myPromise
  .then(data => console.log(data))
  .catch(error => console.error(error));

Callbacks vs Promises – Same Async Operation

Using Callbacks

function fetchUserCallback(callback) {
  setTimeout(() => {
    callback(null, { id: 1, name: "Alice" });
  }, 1000);
}

function fetchPostsCallback(userId, callback) {
  setTimeout(() => {
    callback(null, ["Post 1", "Post 2"]);
  }, 1000);
}

fetchUserCallback((err, user) => {
  if (err) return console.error(err);

  fetchPostsCallback(user.id, (err, posts) => {
    if (err) return console.error(err);

    console.log('User:', user);
    console.log('Posts:', posts);
  });
});

Drawbacks:

  • Nested callbacks → callback hell.
  • Hard to read and maintain.
  • Verbose error handling.

Using Promises

function fetchUser() {
  return new Promise(resolve => {
    setTimeout(() => resolve({ id: 1, name: "Alice" }), 1000);
  });
}

function fetchPosts(userId) {
  return new Promise(resolve => {
    setTimeout(() => resolve(["Post 1", "Post 2"]), 1000);
  });
}

fetchUser()
  .then(user => {
    console.log('User:', user);
    return fetchPosts(user.id);
  })
  .then(posts => {
    console.log('Posts:', posts);
  })
  .catch(err => console.error(err));

Callback Hell Illustration

Callback Hell Example

setTimeout(() => {
  console.log('Step 1 completed');

  setTimeout(() => {
    console.log('Step 2 completed');

    setTimeout(() => {
      console.log('Step 3 completed');

      setTimeout(() => {
        console.log('All steps completed');
      }, 1000);

    }, 1000);

  }, 1000);

}, 1000);

Problems:

  • Deeply nested structure.
  • Difficult to read and maintain.
  • Hard to handle errors at each level.

Cleaner Solutions

Using Named Callback Functions

function step1(callback) {
  setTimeout(() => {
    console.log('Step 1 completed');
    callback();
  }, 1000);
}

function step2(callback) {
  setTimeout(() => {
    console.log('Step 2 completed');
    callback();
  }, 1000);
}

function step3(callback) {
  setTimeout(() => {
    console.log('Step 3 completed');
    callback();
  }, 1000);
}

function allSteps() {
  step1(() => {
    step2(() => {
      step3(() => {
        console.log('All steps completed');
      });
    });
  });
}

allSteps();
  • Slightly cleaner, but still nested.

Using Promises

function step1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('Step 1 completed');
      resolve();
    }, 1000);
  });
}

function step2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('Step 2 completed');
      resolve();
    }, 1000);
  });
}

function step3() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('Step 3 completed');
      resolve();
    }, 1000);
  });
}

step1()
  .then(step2)
  .then(step3)
  .then(() => console.log('All steps completed'))
  .catch(err => console.error(err));
  • Promises provide a flat, readable chain.
  • Errors handled in one place.

Using Async/Await

async function runSteps() {
  try {
    await step1();
    await step2();
    await step3();
    console.log('All steps completed');
  } catch (err) {
    console.error(err);
  }
}

runSteps();
  • Most readable and easy to follow.
  • Cleaner error handling.
  • Looks synchronous but runs asynchronously.

Promise.all & Promise.race

Promise.all – Wait for multiple promises

Promise.all([fetchUser(), fetchPosts(1)])
  .then(([user, posts]) => {
    console.log('User:', user);
    console.log('Posts:', posts);
  });

Promise.race – First settled promise wins

Promise.race([fetchUser(), fetchPosts(1)])
  .then(first => {
    console.log('First resolved:', first);
  });

Error Handling in Async Code

With Promises

myPromise
  .then(data => console.log(data))
  .catch(err => console.error("Error:", err));

With Async/Await

async function fetchData() {
  try {
    const data = await myPromise;
    console.log(data);
  } catch (err) {
    console.error("Error:", err);
  }
}