@typescript-eslint/prefer-promise-reject-errors

Require an Error as the reason when a promise is rejected

needs types

Also called typescript-eslint/prefer-promise-reject-errors, typescript/prefer-promise-reject-errors

A rejected promise turns into a throw at the await. If the reason is a string, the catch block gets a value with no stack trace and no message, and error instanceof Error is false.

The rule checks Promise.reject(...) and calls to reject inside new Promise((resolve, reject) => ...). Rejecting with nothing is reported too.

This is prefer-promise-reject-errors with types. It looks at the reason's type, so a variable that holds a string is reported and a class that extends Error isn't.

With bunfig.toml, turning this rule on runs it in TypeScript files and prefer-promise-reject-errors in JavaScript files, so nothing is reported twice. With an ESLint config, turn the other rule off when you use this one.

bun lint --type-aware turns this rule on in TypeScript files.

Configure#

Off by default. This rule needs types, so turning it on makes bun lint type check your code first.

bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-promise-reject-errors" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-promise-reject-errors" = ["error", {
  allowEmptyReject = false,
  allowThrowingAny = false,
  allowThrowingUnknown = false,
  allow = [], # list of strings or objects
}]

Examples#

example.ts3 problems
function load(url: string, reason: string) {
  if (!url) return Promise.reject("missing url");
  if (url === "/") return Promise.reject();

  return new Promise((resolve, reject) => {
    reject(reason);
  });
}
example.tsNo problems
class HttpError extends Error {}

function load(url: string) {
  if (!url) return Promise.reject(new Error("missing url"));

  return new Promise((resolve, reject) => {
    reject(new HttpError("not found"));
  });
}

Options#

OptionDefaultWhat it does
allowEmptyRejectfalseAllow Promise.reject() and reject() without a reason.
allowThrowingAnyfalseAllow a reason of type any.
allowThrowingUnknownfalseAllow a reason of type unknown.
allow[]Other types that are allowed as a reason.

With strict, the value in a catch block is unknown, so passing it on is reported:

example.ts1 problem
async function retry(task: () => Promise<void>) {
  try {
    await task();
  } catch (error) {
    return Promise.reject(error);
  }
}

With allowThrowingUnknown: true it's allowed:

example.tswith { "allowThrowingUnknown": true }No problems
async function retry(task: () => Promise<void>) {
  try {
    await task();
  } catch (error) {
    return Promise.reject(error);
  }
}

Each allow entry is a type name, or an object that also says where the type comes from: { "from": "file", "name": "Failure" }, { "from": "lib", "name": "Date" }, or { "from": "package", "name": "Failure", "package": "some-package" }. With the first of these, this is allowed:

example.tswith { "allow": [{ "from": "file", "name": "Failure" }] }No problems
interface Failure {
  code: number;
}

function fail(failure: Failure) {
  return Promise.reject(failure);
}