Async Function as Promise Executor

ID

javascript.no_async_promise_executor

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

concurrency, reliability

Description

Reports new Promise(async function …​) and new Promise(async (…​) ⇒ …​). The Promise executor receives resolve / reject and is only meant to call them synchronously. Making it async wraps the body in an extra promise — anything thrown after the first await is captured by that wrapper instead of rejecting the outer Promise, so errors are silently swallowed.

Rationale

  • The outer Promise constructor only catches synchronous throws — once the executor returns its own promise, later rejections are detached from it.

  • The pattern is almost always a misunderstanding: developers reach for async because they want to use await inside the executor, but the right structure is to call an async function and either return its promise directly or chain .then / .catch.

  • It silently breaks unhandledrejection handling and any code that relies on the outer Promise reflecting the inner failure.

// Bad - throws after await disappear into the async wrapper
new Promise(async (resolve, reject) => {
  const data = await fetchData();
  if (!data.ok) throw new Error("nope"); // not seen by the outer Promise
  resolve(data);
});

Remediation

Either return the async work directly, or call resolve / reject synchronously from a non-async executor.

// Good - just return the async function's promise
async function loadData() {
  const data = await fetchData();
  if (!data.ok) throw new Error("nope");
  return data;
}
const p = loadData();

// Good - synchronous executor
new Promise((resolve, reject) => {
  fetchData().then(
    (data) => data.ok ? resolve(data) : reject(new Error("nope")),
    reject
  );
});