Using Promises and Async/Await Effectively in Node.js

Dive into the best use cases for promises and async/await patterns to handle asynchronous operations smoothly.

0 likes
3 views

Rule Content

---
description: Enforce the use of async/await over callbacks and Promises for handling asynchronous operations in Node.js to improve code readability and maintainability.
globs: src/**/*.js
tags: [Node.js, async/await, coding standards]
priority: 2
version: 1.0.0
---

# Using Promises and Async/Await Effectively in Node.js

## Context
- Applicable to all Node.js projects handling asynchronous operations.
- Requires Node.js version 8 or higher.

## Requirements
- **Use async/await for asynchronous operations**: Replace callbacks and Promise chains with async/await syntax to enhance code clarity and reduce nesting.
- **Implement proper error handling**: Utilize try/catch blocks within async functions to manage errors effectively.
- **Avoid mixing callbacks and Promises**: Convert callback-based functions to return Promises, enabling the use of async/await throughout the codebase.
- **Prevent unhandled Promise rejections**: Ensure all Promises are awaited or have catch handlers to handle potential errors.
- **Optimize asynchronous loops**: Use Promise.all() for parallel execution instead of awaiting each operation sequentially within loops.

## Examples
<example>
// Good: Using async/await with proper error handling
async function fetchData() {
  try {
    const data = await getData();
    console.log(data);
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}
</example>

<example type="invalid">
// Bad: Using callbacks leading to callback hell
getData(function(error, data) {
  if (error) {
    console.error('Error fetching data:', error);
  } else {
    processData(data, function(error, processedData) {
      if (error) {
        console.error('Error processing data:', error);
      } else {
        saveData(processedData, function(error) {
          if (error) {
            console.error('Error saving data:', error);
          } else {
            console.log('Data saved successfully');
          }
        });
      }
    });
  }
});
</example>

<example>
// Good: Converting a callback-based function to return a Promise
const fs = require('fs').promises;

async function readFileAsync(filePath) {
  try {
    const data = await fs.readFile(filePath, 'utf8');
    return data;
  } catch (error) {
    throw new Error('Error reading file:', error);
  }
}
</example>

<example type="invalid">
// Bad: Mixing callbacks and Promises
const fs = require('fs');

function readFileAsync(filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, 'utf8', (error, data) => {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}

readFileAsync('example.txt', (error, data) => {
  if (error) {
    console.error('Error reading file:', error);
  } else {
    console.log(data);
  }
});
</example>

<example>
// Good: Using Promise.all() for parallel execution
async function fetchMultipleData(urls) {
  try {
    const fetchPromises = urls.map(url => fetch(url));
    const responses = await Promise.all(fetchPromises);
    const data = await Promise.all(responses.map(response => response.json()));
    return data;
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}
</example>

<example type="invalid">
// Bad: Awaiting each operation sequentially within a loop
async function fetchMultipleData(urls) {
  const data = [];
  for (const url of urls) {
    try {
      const response = await fetch(url);
      const jsonData = await response.json();
      data.push(jsonData);
    } catch (error) {
      console.error('Error fetching data from', url, ':', error);
    }
  }
  return data;
}
</example>