Learning Objectives

  • Identify common Promise pitfalls
  • Use debugging tools effectively
  • Handle unhandled Promise rejections
  • Debug async/await code

Common Promise Pitfalls

1. Forgetting to Return

Problem:

function getData() {
  fetchUser(1).then(user => {
    return user.name; // This return goes to .then(), not getData()
  });
  // getData() returns undefined!
}

Solution:

function getData() {
  return fetchUser(1).then(user => {
    return user.name;
  });
}

2. Nesting Promises (Promise Hell)

Problem:

fetchUser(1).then(user => {
  fetchPosts(user.id).then(posts => {
    fetchComments(posts[0].id).then(comments => {
      console.log(comments);
    });
  });
});

Solution:

fetchUser(1)
  .then(user => fetchPosts(user.id))
  .then(posts => fetchComments(posts[0].id))
  .then(comments => console.log(comments));

3. Not Handling Errors

Problem:

fetchUser(1).then(user => {
  console.log(user);
});
// Unhandled rejection if fetchUser fails!

Solution:

fetchUser(1)
  .then(user => console.log(user))
  .catch(error => console.error('Error:', error));

4. Creating Unnecessary Promises

Problem:

function getUser(id) {
  return new Promise((resolve, reject) => {
    fetchUser(id)
      .then(user => resolve(user))
      .catch(error => reject(error));
  });
}

Solution:

function getUser(id) {
  return fetchUser(id); // Already returns a Promise!
}

5. Sequential When Parallel is Possible

Problem (Slow):

const user = await fetchUser(1);      // 1s
const posts = await fetchPosts(1);    // 1s
const comments = await fetchComments(1); // 1s
// Total: 3 seconds

Solution (Fast):

const [user, posts, comments] = await Promise.all([
  fetchUser(1),
  fetchPosts(1),
  fetchComments(1)
]);
// Total: 1 second

Debugging Tools and Techniques

1. Console Logging

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

2. Debugger Statement

async function getData() {
  const user = await fetchUser(1);
  debugger; // Pause execution here
  const posts = await fetchPosts(user.id);
  return posts;
}

3. Chrome DevTools

4. Unhandled Rejection Tracking

// Node.js
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection at:', promise);
  console.error('Reason:', reason);
});

// Browser
window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled rejection:', event.reason);
  event.preventDefault();
});

Debugging Async/Await

Common Async/Await Mistakes

1. Forgetting async Keyword

// ❌ Error: await outside async function
function getData() {
  const user = await fetchUser(1);
}
// ✅ Correct
async function getData() {
  const user = await fetchUser(1);
}

2. Not Catching Errors

// ❌ Unhandled rejection
async function getData() {
  const user = await fetchUser(1);
  return user;
}
// ✅ Proper error handling
async function getData() {
  try {
    const user = await fetchUser(1);
    return user;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

Performance Debugging

Measuring Execution Time

async function measurePerformance() {
  console.time('Sequential');
  const user1 = await fetchUser(1);
  const user2 = await fetchUser(2);
  const user3 = await fetchUser(3);
  console.timeEnd('Sequential'); // ~3000ms
  
  console.time('Parallel');
  const [u1, u2, u3] = await Promise.all([
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
  ]);
  console.timeEnd('Parallel'); // ~1000ms
}

Memory Leak Detection

// Bad: Creates memory leak
const promises = [];
setInterval(() => {
  promises.push(fetchData()); // Never cleaned up!
}, 1000);

// Good: Limit array size
const MAX_PROMISES = 100;
const promises = [];
setInterval(() => {
  if (promises.length >= MAX_PROMISES) {
    promises.shift(); // Remove oldest
  }
  promises.push(fetchData());
}, 1000);

Best Practices for Debugging

  1. Always handle errors - Use .catch() or try/catch
  2. Return Promises - Don't forget to return in functions
  3. Avoid nesting - Keep chains flat
  4. Use async/await - More readable and easier to debug
  5. Add logging - Track Promise flow
  6. Use debugger - Leverage browser DevTools
  7. Test edge cases - Test both success and failure
  8. Monitor unhandled rejections - Set up global handlers

Key Takeaways

  • ✅ Always return Promises from functions
  • ✅ Avoid nesting - use flat chains
  • ✅ Always handle errors with .catch() or try/catch
  • ✅ Use Promise.all() for parallel operations
  • ✅ Track unhandled rejections in production
  • ✅ Use browser DevTools for debugging
  • ✅ Measure performance to identify bottlenecks

Next Steps

Ready to put it all together? Check out the course project in Section 8!