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
Promiseconstructor 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
asyncbecause they want to useawaitinside the executor, but the right structure is to call anasyncfunction and either return its promise directly or chain.then/.catch. -
It silently breaks
unhandledrejectionhandling 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
);
});