Frontend Development 8 min read

Recommended ESLint Rules for Writing Good Asynchronous JavaScript Code

This article presents a collection of ESLint rules for JavaScript asynchronous programming, explaining why patterns like async promise executors, awaiting in loops, returning values from Promise constructors, and others should be avoided, and provides correct code examples to improve readability, performance, and error handling.

IT Services Circle
IT Services Circle
IT Services Circle
Recommended ESLint Rules for Writing Good Asynchronous JavaScript Code

Today we recommend several best‑practice ESLint rules for writing good asynchronous JavaScript code; each rule targets a specific scenario and can be enabled in your ESLint configuration.

no-async-promise-executor

Do not pass an async function to the new Promise constructor because the resulting Promise may not reject on errors and the extra wrapper is unnecessary.

// ❌
new Promise(async (resolve, reject) => {});

// ✅
new Promise((resolve, reject) => {});

Using async inside the constructor prevents proper error propagation.

no-await-in-loop

Avoid using await inside loops; it blocks the event loop and defeats JavaScript's concurrency model.

// ❌
for (const url of urls) {
  const response = await fetch(url);
}

// ✅
const responses = [];
for (const url of urls) {
  const response = fetch(url);
  responses.push(response);
}
await Promise.all(responses);

Running the async tasks concurrently greatly improves execution efficiency.

no-promise-executor-return

Do not return a value from a Promise executor; the return value is ignored and does not affect the Promise state.

// ❌
new Promise((resolve, reject) => {
  return result;
});

// ✅
new Promise((resolve, reject) => {
  resolve(result);
});

require-atomic-updates

Combining assignment with await can cause race conditions. For example, the final value of totalPosts may be 3 or 5 instead of 8.

// ❌
let totalPosts = 0;
async function addPosts(userId) {
  totalPosts += await getPosts(userId);
}
await Promise.all([addPosts(1), addPosts(2)]);
console.log('Post count:', totalPosts);

// ✅
let totalPosts = 0;
async function addPosts(userId) {
  const posts = await getPosts(userId);
  totalPosts += posts; // variable is read and immediately updated
}
await Promise.all([addPosts(1), addPosts(2)]);
console.log('Post count:', totalPosts);

max-nested-callbacks

Prevent callback hell by limiting nesting depth and refactoring callbacks into Promise chains with async/await .

// ❌ (deeply nested callbacks)
async1((err, result1) => {
  async2(result1, (err, result2) => {
    async3(result2, (err, result3) => {
      async4(result3, (err, result4) => {
        console.log(result4);
      });
    });
  });
});

// ✅ (sequential awaits)
const result1 = await asyncPromise1();
const result2 = await asyncPromise2(result1);
const result3 = await asyncPromise3(result2);
const result4 = await asyncPromise4(result3);
console.log(result4);

no-return-await

When returning a Promise from an async function, omit the unnecessary await unless you need to catch errors inside a try…catch block.

// ❌
async () => {
  return await getUser(userId);
};

// ✅
async () => {
  return getUser(userId);
};

prefer-promise-reject-errors

When rejecting a Promise, always reject with an Error object to preserve stack traces.

// ❌
Promise.reject('An error occurred');

// ✅
Promise.reject(new Error('An error occurred'));

node/handle-callback-err

In Node.js callbacks, always handle the first err argument to avoid uncaught exceptions.

// ❌
function callback(err, data) {
  console.log(data);
}

// ✅
function callback(err, data) {
  if (err) {
    console.log(err);
    return;
  }
  console.log(data);
}

node/no-sync

Avoid synchronous Node.js APIs when asynchronous alternatives exist, as sync calls block the event loop.

// ❌
const file = fs.readFileSync(path);

// ✅
const file = await fs.readFile(path);

@typescript-eslint/await-thenable

Do not await non‑Promise values; ensure the awaited expression returns a Promise.

// ❌
function getValue() {
  return someValue;
}
await getValue();

// ✅
async function getValue() {
  return someValue;
}
await getValue();

@typescript-eslint/no-floating-promises

Always attach a .catch handler to Promises to avoid unhandled rejections.

// ❌
myPromise()
  .then(() => {});

// ✅
myPromise()
  .then(() => {})
  .catch(() => {});

@typescript-eslint/no-misused-promises

Do not use a Promise directly in a boolean context; await it first or assign it to a variable.

// ❌
if (getUserFromDB()) {}

// ✅
if (await getUserFromDB()) {}

// Better
const user = await getUserFromDB();
if (user) {}

Reference: Linting rules for asynchronous code in JavaScript

JavaScriptBest PracticesasyncPromiseeslint
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.