Redundant Promise.resolve / Promise.reject

ID

javascript.promise_resolve_in_async

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Best Practice

Language

JavaScript

Tags

best-practice, concurrency, reliability

Description

Reports Promise.resolve / Promise.reject written where the surrounding construct already settles the promise — returned from an async function, or called as a statement inside a new Promise(…​) executor:

async function load(id) {
  if (!id) return Promise.reject(new Error("no id"));   // FLAW
  return Promise.resolve(cache.get(id));                // FLAW
}

new Promise((resolve, reject) => {
  if (!ready) { Promise.reject(error); }                // FLAW
});

Rationale

In an async function. The function already wraps whatever it returns in a resolved promise and whatever it throws in a rejected one. The explicit call therefore puts a promise inside a promise and costs one more microtask turn per call.

The readability cost is larger than the runtime one. return Promise.reject(e) is a rejection written as a return: a try/catch around the call reads as if it could not fire, and a refactor that drops the return — an easy edit, since it looks like an ordinary value — silently turns the rejection into an unhandled one.

In a promise executor. Promise.reject(e) builds a second, unrelated promise and throws it away. The outer promise is never settled, so the caller waits forever, and the discarded rejection surfaces as an unhandled-rejection warning with nothing connecting it to the code that caused it. What was meant is the executor’s own resolve / reject parameter.

Remediation

In an async function, return the value and throw the error:

async function load(id) {
  if (!id) throw new Error("no id");
  return cache.get(id);
}

In an executor, call the parameter the executor was handed:

new Promise((resolve, reject) => {
  if (!ready) { reject(error); return; }
  resolve(value);
});

Notes

Only the two shapes above are reported. The microtask idiom Promise.resolve().then(…​) and a Promise.resolve(x) passed on as an argument — Promise.all([Promise.resolve(1), p]) — are left alone: in both, the value is used rather than wrapped and discarded.

A Promise.resolve(…​) returned from a plain (non-async) function is the normal way to give a synchronous value a promise interface, and is not reported.

© 2026 Xygeni. All rights reserved