Skip to content

Async

Rule

Use async/await keyword instead of chaining promises

  • Readability - async/await makes asynchronous code look and behave more like synchronous code, which is easier to understand at a glance.
  • Error handling - With async/await, you can use familiar try/catch blocks instead of attaching .catch() handlers, making error handling more intuitive.
  • Debugging - Stack traces are more meaningful with async/await, making it easier to locate errors.
  • Control flow - async/await makes complex control flows like conditional logic, loops, and variable scoping more straightforward.
  • Avoiding callback hell - Deep promise chains can become difficult to read and maintain, similar to callback hell.
  • Intermediate values - You can access intermediate results from previous async operations without nesting.

https://www.youtube.com/watch?v=Pz2cL01bmwQ&t=629s

Examples

🚨 DON’T

// Complex promise chaining
const handleData = () => {
  fetchData()
    .then((data) => processData(data))
    .then((result) => displayResult(result))
    .catch((error) => handleError(error));
};

// Nested promise chains (callback hell 2.0)
const processUserData = (userId) => {
  return getUserById(userId).then((user) => {
    return getProfilePicture(user.profileId).then((picture) => {
      return getUserPreferences(user.id).then((preferences) => {
        return {
          user,
          picture,
          preferences,
        };
      });
    });
  });
};

// Conditional logic with promises
const processData = (shouldCompress) => {
  return fetchData().then((data) => {
    if (shouldCompress) {
      return compressData(data).then((compressed) => saveData(compressed));
    } else {
      return saveData(data);
    }
  });
};

// Mixed async patterns
const handleRequest = () => {
  fetchUser().then(async (user) => {
    const profile = await getProfile(user.id);
    return processProfile(profile);
  });
};

✅ DO

// Clean async/await syntax
const handleData = async () => {
  try {
    const data = await fetchData();
    const result = await processData(data);

    await displayResult(result);
  } catch (error) {
    handleError(error);
  }
};

// Parallel execution for independent operations
const loadDashboard = async () => {
  try {
    const [user, notifications, settings] = await Promise.all([
      getUser(),
      getNotifications(),
      getSettings(),
    ]);

    return { user, notifications, settings };
  } catch (error) {
    console.error("Dashboard loading failed:", error);
    throw error;
  }
};

// Clear sequential flow with intermediate values
const processUserData = async (userId) => {
  try {
    const user = await getUserById(userId);
    const picture = await getProfilePicture(user.profileId);
    const preferences = await getUserPreferences(user.id);

    return {
      user,
      picture,
      preferences,
    };
  } catch (error) {
    console.error("Failed to process user data:", error);
    throw error;
  }
};

// Simple conditional logic
const processData = async (shouldCompress) => {
  try {
    const data = await fetchData();
    const finalData = shouldCompress ? await compressData(data) : data;

    return saveData(finalData);
  } catch (error) {
    console.error("Data processing failed:", error);
    throw error;
  }
};

// Proper loop handling with parallel execution
const processItems = async (items) => {
  try {
    return Promise.all(items.map((item) => processItem(item)));
  } catch (error) {
    console.error("Item processing failed:", error);
    throw error;
  }
};